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

Allow state skipping for dependent encoding profiles #217

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 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 src/Application/Controller/XMLRPC/Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,7 @@ public function setCommenceTicketState($ticket_id) {
throw new Exception(__FUNCTION__.': ticket is not in initial state!',1304);
}

$commenceState = ProjectTicketState::getCommenceState($ticket['project_id'], $ticket['ticket_type']);
$commenceState = ProjectTicketState::getCommenceState($ticket_id);
if(!$commenceState) {
throw new Exception(__FUNCTION__.': ticket has no commence state!',1305);
}
Expand Down
1 change: 1 addition & 0 deletions src/Application/Migrations/04_projects.sql
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ CREATE TABLE tbl_project_ticket_state
ticket_type enum_ticket_type NOT NULL,
ticket_state enum_ticket_state NOT NULL,
service_executable boolean NOT NULL DEFAULT false,
skip_on_dependent boolean NOT NULL DEFAULT false,
CONSTRAINT tbl_project_ticket_state_pk PRIMARY KEY (project_id, ticket_type, ticket_state),
CONSTRAINT tbl_project_ticket_state_project_fk FOREIGN KEY (project_id)
REFERENCES tbl_project (id) MATCH SIMPLE
Expand Down
4 changes: 2 additions & 2 deletions src/Application/Migrations/06_tickets.sql
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ $BODY$
DECLARE
next_state record;
BEGIN
next_state := ticket_state_next(NEW.project_id, NEW.ticket_type, NEW.ticket_state);
next_state := ticket_state_next(NEW.id, NEW.ticket_state);

NEW.ticket_state_next := next_state.ticket_state;
NEW.service_executable := next_state.service_executable;
Expand Down Expand Up @@ -95,7 +95,7 @@ $BODY$
(progress, ticket_state_next, service_executable)
= (tp, (n).ticket_state, (n).service_executable)
FROM (
SELECT id, ticket_state_next(t2.project_id, t2.ticket_type, t2.ticket_state) AS n, ticket_progress(t2.id) as tp
SELECT id, ticket_state_next(t2.id) AS n, ticket_progress(t2.id) as tp
FROM tbl_ticket t2
WHERE t2.project_id = param_project_id AND param_project_id IS NOT NULL
) AS x
Expand Down
85 changes: 56 additions & 29 deletions src/Application/Migrations/15_function_ticket_state.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ BEGIN;

SET ROLE TO postgres;

CREATE OR REPLACE FUNCTION ticket_state_next(param_project_id bigint, param_ticket_type enum_ticket_type, param_ticket_state enum_ticket_state)
CREATE OR REPLACE FUNCTION ticket_state_next(param_ticket_id bigint, param_ticket_state enum_ticket_state DEFAULT NULL)
RETURNS TABLE(ticket_state enum_ticket_state, service_executable boolean) AS
$$
DECLARE
Expand All @@ -11,26 +11,35 @@ BEGIN
SELECT
pts.ticket_state, pts.service_executable
FROM
tbl_ticket_state ts1
tbl_ticket t
JOIN
tbl_project_ticket_state pts ON pts.ticket_type = ts1.ticket_type AND pts.ticket_state = ts1.ticket_state
tbl_ticket_state ts_this ON ts_this.ticket_type = t.ticket_type AND ts_this.ticket_state = COALESCE(param_ticket_state, t.ticket_state)
JOIN
tbl_ticket_state ts2 ON ts1.ticket_type = ts2.ticket_type AND ts1.sort > ts2.sort
tbl_project_ticket_state pts ON pts.project_id = t.project_id AND pts.ticket_type = t.ticket_type
JOIN
tbl_ticket_state ts_other ON ts_other.ticket_type = pts.ticket_type AND ts_other.ticket_state = pts.ticket_state
WHERE
pts.project_id = param_project_id AND
ts2.ticket_type = param_ticket_type AND
ts2.ticket_state = param_ticket_state
ORDER BY
ts1.sort ASC
t.id = param_ticket_id AND
ts_other.sort > ts_this.sort AND
(pts.skip_on_dependent = FALSE OR
( /* is master encoding ticket */
SELECT ep.depends_on
FROM tbl_ticket t
INNER JOIN tbl_encoding_profile_version epv ON epv.id = t.encoding_profile_version_id
INNER JOIN tbl_encoding_profile ep ON ep.id = epv.encoding_profile_id
WHERE t.id = param_ticket_id
) IS NULL )
ORDER BY ts_other.sort ASC
LIMIT 1;
IF NOT FOUND THEN
RETURN QUERY SELECT NULL::enum_ticket_state, false;
END IF;

IF NOT FOUND THEN
RETURN QUERY SELECT NULL::enum_ticket_state, false;
END IF;
END
$$
LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION ticket_state_previous(param_project_id bigint, param_ticket_type enum_ticket_type, param_ticket_state enum_ticket_state)
CREATE OR REPLACE FUNCTION ticket_state_previous(param_ticket_id bigint, param_ticket_state enum_ticket_state DEFAULT NULL)
RETURNS TABLE(ticket_state enum_ticket_state, service_executable boolean) AS
$$
DECLARE
Expand All @@ -39,21 +48,30 @@ BEGIN
SELECT
pts.ticket_state, pts.service_executable
FROM
tbl_ticket_state ts1
tbl_ticket t
JOIN
tbl_ticket_state ts_this ON ts_this.ticket_type = t.ticket_type AND ts_this.ticket_state = COALESCE(param_ticket_state, t.ticket_state)
JOIN
tbl_project_ticket_state pts ON pts.ticket_type = ts1.ticket_type AND pts.ticket_state = ts1.ticket_state
tbl_project_ticket_state pts ON pts.project_id = t.project_id AND pts.ticket_type = t.ticket_type
JOIN
tbl_ticket_state ts2 ON ts1.ticket_type = ts2.ticket_type AND ts1.sort < ts2.sort
tbl_ticket_state ts_other ON ts_other.ticket_type = pts.ticket_type AND ts_other.ticket_state = pts.ticket_state
WHERE
pts.project_id = param_project_id AND
ts2.ticket_type = param_ticket_type AND
ts2.ticket_state = param_ticket_state
ORDER BY
ts1.sort DESC
t.id = param_ticket_id AND
ts_other.sort < ts_this.sort AND
(pts.skip_on_dependent = FALSE OR
( /* is master encoding ticket */
SELECT ep.depends_on
FROM tbl_ticket t
JOIN tbl_encoding_profile_version epv ON epv.id = t.encoding_profile_version_id
JOIN tbl_encoding_profile ep ON ep.id = epv.encoding_profile_id
WHERE t.id = param_ticket_id
) IS NULL )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second time the exact same subquery is used. Would it be worth it to have is is_master_ticket(ticket_id)?

ORDER BY ts_other.sort DESC
LIMIT 1;
IF NOT FOUND THEN
RETURN QUERY SELECT NULL::enum_ticket_state, false;
END IF;

IF NOT FOUND THEN
RETURN QUERY SELECT NULL::enum_ticket_state, false;
END IF;
END
$$
LANGUAGE plpgsql;
Expand Down Expand Up @@ -96,22 +114,31 @@ END
$$
LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION ticket_state_commence(param_project_id bigint, param_ticket_type enum_ticket_type)
CREATE OR REPLACE FUNCTION ticket_state_commence(param_ticket_id bigint)
RETURNS enum_ticket_state AS
$$
DECLARE
var_project_id bigint;
var_ticket_type enum_ticket_type;
ret enum_ticket_state;
next_state record;
BEGIN
SELECT
project_id, ticket_type INTO var_project_id, var_ticket_type
FROM
tbl_ticket t
WHERE
t.id = param_ticket_id;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if that ticket_id is not found?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then ticket_state_initial(NULL, NULL) is called which results in

RAISE WARNING 'No default ticket state for project!';

which returns NULL, so the while loop is not entered and function returns NULL.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, nothing breaks but the warning message is misleading. Mh.
Maybe adding a IF NOT FOUND THEN clause might be worth it.


-- special case: meta ticket, since it has no serviceable states
IF param_ticket_type = 'meta' THEN
IF var_ticket_type = 'meta' THEN
RETURN 'staged'::enum_ticket_state;
END IF;

ret := (SELECT ticket_state_initial(param_project_id, param_ticket_type));
ret := (SELECT ticket_state_initial(var_project_id, var_ticket_type));

WHILE ret IS NOT NULL LOOP
SELECT * INTO next_state FROM ticket_state_next(param_project_id, param_ticket_type, ret);
SELECT * INTO next_state FROM ticket_state_next(param_ticket_id, ret);
IF NOT FOUND THEN
ret := NULL;
EXIT;
Expand Down Expand Up @@ -215,4 +242,4 @@ END
$$
LANGUAGE plpgsql;

COMMIT;
COMMIT;
184 changes: 184 additions & 0 deletions src/Application/Migrations/__2019-09-29_skip_dependent.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
BEGIN;

SET ROLE TO postgres;

ALTER TABLE tbl_project_ticket_state
ADD COLUMN skip_on_dependent boolean NOT NULL DEFAULT false;

ALTER TABLE tbl_ticket_state
ADD COLUMN skippable_on_dependent boolean NOT NULL DEFAULT false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That column only exists in the migration patch?!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And it only seems to be used when displaying the states, but a database user would be allowed to set that flag on states not marked as skippable? or did I miss something

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And it only seems to be used when displaying the states, but a database user would be allowed to set that flag on states not marked as skippable?

It belongs to the state, so it's stored together with states. I'm not sure we need additional validation if the state marked to skip in a project is skippable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That column only exists in the migration patch?!

Fixed, thanks.



UPDATE tbl_ticket_state
SET skippable_on_dependent = true
WHERE ticket_type = 'encoding'
AND ticket_state IN ('checking', 'checked', 'postprocessing', 'postprocessed', 'ready to release');

DROP FUNCTION IF EXISTS ticket_state_next(bigint);
DROP FUNCTION IF EXISTS ticket_state_next(bigint, enum_ticket_type);
DROP FUNCTION IF EXISTS ticket_state_next(bigint, enum_ticket_type, enum_ticket_state);

DROP FUNCTION IF EXISTS ticket_state_previous(bigint);
DROP FUNCTION IF EXISTS ticket_state_previous(bigint, enum_ticket_type);
DROP FUNCTION IF EXISTS ticket_state_previous(bigint, enum_ticket_type, enum_ticket_state);

DROP FUNCTION IF EXISTS ticket_state_commence(bigint, enum_ticket_type);
DROP FUNCTION IF EXISTS ticket_state_commence(bigint);


CREATE OR REPLACE FUNCTION update_ticket_next_state()
RETURNS trigger AS
$BODY$
DECLARE
next_state record;
BEGIN
next_state := ticket_state_next(NEW.id, NEW.ticket_state);

NEW.ticket_state_next := next_state.ticket_state;
NEW.service_executable := next_state.service_executable;

RETURN NEW;
END
$BODY$
LANGUAGE plpgsql VOLATILE;


CREATE OR REPLACE FUNCTION update_all_tickets_progress_and_next_state(param_project_id bigint)
RETURNS VOID AS
$BODY$
BEGIN

UPDATE tbl_ticket t SET
(progress, ticket_state_next, service_executable)
= (tp, (n).ticket_state, (n).service_executable)
FROM (
SELECT id, ticket_state_next(t2.id) AS n, ticket_progress(t2.id) as tp
FROM tbl_ticket t2
WHERE t2.project_id = param_project_id AND param_project_id IS NOT NULL
) AS x
WHERE t.id = x.id;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why that needs a subquery?!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function "ticket_state_next" shall not be called twice per row. No idea how to use the result of a function call in a SET block of an UPDATE statement, since you need the state and its executable flag separately.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see


END;
$BODY$
LANGUAGE plpgsql VOLATILE;


CREATE OR REPLACE FUNCTION ticket_state_next(param_ticket_id bigint, param_ticket_state enum_ticket_state DEFAULT NULL)
RETURNS TABLE(ticket_state enum_ticket_state, service_executable boolean) AS
$$
DECLARE
BEGIN
RETURN QUERY
SELECT
pts.ticket_state, pts.service_executable
FROM
tbl_ticket t
JOIN
tbl_ticket_state ts_this ON ts_this.ticket_type = t.ticket_type AND ts_this.ticket_state = COALESCE(param_ticket_state, t.ticket_state)
JOIN
tbl_project_ticket_state pts ON pts.project_id = t.project_id AND pts.ticket_type = t.ticket_type
JOIN
tbl_ticket_state ts_other ON ts_other.ticket_type = pts.ticket_type AND ts_other.ticket_state = pts.ticket_state
WHERE
t.id = param_ticket_id AND
ts_other.sort > ts_this.sort AND
(pts.skip_on_dependent = FALSE OR
( /* is master encoding ticket */
SELECT ep.depends_on
FROM tbl_ticket t
INNER JOIN tbl_encoding_profile_version epv ON epv.id = t.encoding_profile_version_id
INNER JOIN tbl_encoding_profile ep ON ep.id = epv.encoding_profile_id
WHERE t.id = param_ticket_id
) IS NULL )
ORDER BY ts_other.sort ASC
LIMIT 1;

IF NOT FOUND THEN
RETURN QUERY SELECT NULL::enum_ticket_state, false;
END IF;
END
$$
LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION ticket_state_previous(param_ticket_id bigint, param_ticket_state enum_ticket_state DEFAULT NULL)
RETURNS TABLE(ticket_state enum_ticket_state, service_executable boolean) AS
$$
DECLARE
BEGIN
RETURN QUERY
SELECT
pts.ticket_state, pts.service_executable
FROM
tbl_ticket t
JOIN
tbl_ticket_state ts_this ON ts_this.ticket_type = t.ticket_type AND ts_this.ticket_state = COALESCE(param_ticket_state, t.ticket_state)
JOIN
tbl_project_ticket_state pts ON pts.project_id = t.project_id AND pts.ticket_type = t.ticket_type
JOIN
tbl_ticket_state ts_other ON ts_other.ticket_type = pts.ticket_type AND ts_other.ticket_state = pts.ticket_state
WHERE
t.id = param_ticket_id AND
ts_other.sort < ts_this.sort AND
(pts.skip_on_dependent = FALSE OR
( /* is master encoding ticket */
SELECT ep.depends_on
FROM tbl_ticket t
JOIN tbl_encoding_profile_version epv ON epv.id = t.encoding_profile_version_id
JOIN tbl_encoding_profile ep ON ep.id = epv.encoding_profile_id
WHERE t.id = param_ticket_id
) IS NULL )
ORDER BY ts_other.sort DESC
LIMIT 1;

IF NOT FOUND THEN
RETURN QUERY SELECT NULL::enum_ticket_state, false;
END IF;
END
$$
LANGUAGE plpgsql;


CREATE OR REPLACE FUNCTION ticket_state_commence(param_ticket_id bigint)
RETURNS enum_ticket_state AS
$$
DECLARE
var_project_id bigint;
var_ticket_type enum_ticket_type;
ret enum_ticket_state;
next_state record;
BEGIN
SELECT
project_id, ticket_type INTO var_project_id, var_ticket_type
FROM
tbl_ticket t
WHERE
t.id = param_ticket_id;

-- special case: meta ticket, since it has no serviceable states
IF var_ticket_type = 'meta' THEN
RETURN 'staged'::enum_ticket_state;
END IF;

ret := (SELECT ticket_state_initial(var_project_id, var_ticket_type));

WHILE ret IS NOT NULL LOOP
SELECT * INTO next_state FROM ticket_state_next(param_ticket_id, ret);
IF NOT FOUND THEN
ret := NULL;
EXIT;
END IF;

-- exit, if serviceable state is found
EXIT WHEN next_state.service_executable IS TRUE;

-- otherwise set current state as possible commence state
ret := next_state.ticket_state;
END LOOP;

RETURN ret;
END
$$
LANGUAGE plpgsql;


COMMIT;
27 changes: 3 additions & 24 deletions src/Application/Model/ProjectTicketState.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,31 +20,10 @@ class ProjectTicketState extends Model {
]
];

// TODO: use Ticket::queryNextState / queryPreviousState?
public static function getNextState($project, $type, $state) {
public static function getCommenceState($ticket) {
$handle = Database::$Instance->query(
'SELECT * FROM ticket_state_next(?, ?, ?)',
[$project, $type, $state]
);
$row = $handle->fetch();

return ($row === false)? null : $row;
}

public static function getPreviousState($project, $type, $state) {
$handle = Database::$Instance->query(
'SELECT * FROM ticket_state_previous(?, ?, ?)',
[$project, $type, $state]
);

$row = $handle->fetch();
return ($row === false)? null : $row;
}

public static function getCommenceState($project, $type) {
$handle = Database::$Instance->query(
'SELECT ticket_state_commence(?, ?)',
[$project, $type]
'SELECT ticket_state_commence(?)',
[$ticket]
);

return $handle->fetch()['ticket_state_commence'];
Expand Down
Loading