From 7134d04262512a65b59493ff2a009893fddc917d Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 7 Jun 2016 09:13:38 +0200 Subject: [PATCH] Adding migrations --- .../0046_triggers_to_update_tags_colors.py | 192 ++++++++++++++++++ tests/integration/test_projects.py | 24 +++ 2 files changed, 216 insertions(+) create mode 100644 taiga/projects/migrations/0046_triggers_to_update_tags_colors.py diff --git a/taiga/projects/migrations/0046_triggers_to_update_tags_colors.py b/taiga/projects/migrations/0046_triggers_to_update_tags_colors.py new file mode 100644 index 00000000..28296036 --- /dev/null +++ b/taiga/projects/migrations/0046_triggers_to_update_tags_colors.py @@ -0,0 +1,192 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.2 on 2016-06-07 06:19 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0045_merge'), + ] + + operations = [ + # Function: Reduce a multidimensional array only on its first level + migrations.RunSQL( + """ + CREATE OR REPLACE FUNCTION public.reduce_dim(anyarray) + RETURNS SETOF anyarray + AS $function$ + DECLARE + s $1%TYPE; + BEGIN + FOREACH s SLICE 1 IN ARRAY $1 LOOP + RETURN NEXT s; + END LOOP; + RETURN; + END; + $function$ + LANGUAGE plpgsql IMMUTABLE; + """ + ), + # Function: aggregates multi dimensional arrays + migrations.RunSQL( + """ + DROP AGGREGATE IF EXISTS array_agg_mult (anyarray); + CREATE AGGREGATE array_agg_mult (anyarray) ( + SFUNC = array_cat + ,STYPE = anyarray + ,INITCOND = '{}' + ); + """ + ), + # Function: array_distinct + migrations.RunSQL( + """ + CREATE OR REPLACE FUNCTION array_distinct(anyarray) + RETURNS anyarray AS $$ + SELECT ARRAY(SELECT DISTINCT unnest($1)) + $$ LANGUAGE sql; + """ + ), + # Rebuild the color tags so it's consisten in any project + migrations.RunSQL( + """ + WITH + tags_colors AS ( + SELECT id project_id, reduce_dim(tags_colors) tags_colors + FROM projects_project + WHERE tags_colors != '{}' + ), + tags AS ( + SELECT unnest(tags) tag, NULL color, project_id FROM userstories_userstory + UNION + SELECT unnest(tags) tag, NULL color, project_id FROM tasks_task + UNION + SELECT unnest(tags) tag, NULL color, project_id FROM issues_issue + UNION + SELECT unnest(tags) tag, NULL color, id project_id FROM projects_project + ), + rebuilt_tags_colors AS ( + SELECT tags.project_id project_id, + array_agg_mult(ARRAY[[tags.tag, tags_colors.tags_colors[2]]]) tags_colors + FROM tags + LEFT JOIN tags_colors ON + tags_colors.project_id = tags.project_id AND + tags_colors[1] = tags.tag + GROUP BY tags.project_id + ) + UPDATE projects_project + SET tags_colors = rebuilt_tags_colors.tags_colors + FROM rebuilt_tags_colors + WHERE rebuilt_tags_colors.project_id = projects_project.id; + """ + ), + # Trigger for auto updating projects_project.tags_colors + migrations.RunSQL( + """ + CREATE OR REPLACE FUNCTION update_project_tags_colors() + RETURNS trigger AS $update_project_tags_colors$ + DECLARE + tags text[]; + project_tags_colors text[]; + tag_color text[]; + project_tags text[]; + tag text; + project_id integer; + BEGIN + tags := NEW.tags::text[]; + project_id := NEW.project_id::integer; + project_tags := '{}'; + + -- Read project tags_colors into project_tags_colors + SELECT projects_project.tags_colors INTO project_tags_colors + FROM projects_project + WHERE id = project_id; + + -- Extract just the project tags to project_tags_colors + IF project_tags_colors != ARRAY[]::text[] THEN + FOREACH tag_color SLICE 1 in ARRAY project_tags_colors + LOOP + project_tags := array_append(project_tags, tag_color[1]); + END LOOP; + END IF; + + -- Add to project_tags_colors the new tags + IF tags IS NOT NULL THEN + FOREACH tag in ARRAY tags + LOOP + IF tag != ALL(project_tags) THEN + project_tags_colors := array_cat(project_tags_colors, + ARRAY[ARRAY[tag, NULL]]); + END IF; + END LOOP; + END IF; + + -- Save the result in the tags_colors column + UPDATE projects_project + SET tags_colors = project_tags_colors + WHERE id = project_id; + + RETURN NULL; + END; $update_project_tags_colors$ + LANGUAGE plpgsql; + """ + ), + + # Execute trigger after user_story update + migrations.RunSQL( + """ + DROP TRIGGER IF EXISTS update_project_tags_colors_on_userstory_update ON userstories_userstory; + CREATE TRIGGER update_project_tags_colors_on_userstory_update + AFTER UPDATE ON userstories_userstory + FOR EACH ROW EXECUTE PROCEDURE update_project_tags_colors(); + """ + ), + # Execute trigger after user_story insert + migrations.RunSQL( + """ + DROP TRIGGER IF EXISTS update_project_tags_colors_on_userstory_insert ON userstories_userstory; + CREATE TRIGGER update_project_tags_colors_on_userstory_insert + AFTER INSERT ON userstories_userstory + FOR EACH ROW EXECUTE PROCEDURE update_project_tags_colors(); + """ + ), + # Execute trigger after task update + migrations.RunSQL( + """ + DROP TRIGGER IF EXISTS update_project_tags_colors_on_task_update ON tasks_task; + CREATE TRIGGER update_project_tags_colors_on_task_update + AFTER UPDATE ON tasks_task + FOR EACH ROW EXECUTE PROCEDURE update_project_tags_colors(); + """ + ), + # Execute trigger after task insert + migrations.RunSQL( + """ + DROP TRIGGER IF EXISTS update_project_tags_colors_on_task_insert ON tasks_task; + CREATE TRIGGER update_project_tags_colors_on_task_insert + AFTER INSERT ON tasks_task + FOR EACH ROW EXECUTE PROCEDURE update_project_tags_colors(); + """ + ), + # Execute trigger after issue update + migrations.RunSQL( + """ + DROP TRIGGER IF EXISTS update_project_tags_colors_on_issue_update ON issues_issue; + CREATE TRIGGER update_project_tags_colors_on_issue_update + AFTER UPDATE ON issues_issue + FOR EACH ROW EXECUTE PROCEDURE update_project_tags_colors(); + """ + ), + # Execute trigger after issue insert + migrations.RunSQL( + """ + DROP TRIGGER IF EXISTS update_project_tags_colors_on_issue_insert ON issues_issue; + CREATE TRIGGER update_project_tags_colors_on_issue_insert + AFTER INSERT ON issues_issue + FOR EACH ROW EXECUTE PROCEDURE update_project_tags_colors(); + """ + ), + ] diff --git a/tests/integration/test_projects.py b/tests/integration/test_projects.py index 9f91cd21..4c2e121e 100644 --- a/tests/integration/test_projects.py +++ b/tests/integration/test_projects.py @@ -1852,3 +1852,27 @@ def test_delete_project_with_celery_disabled(client, settings): response = client.json.delete(url) assert response.status_code == 204 assert Project.objects.filter(id=project.id).count() == 0 + + +def test_color_tags_project_fired_on_element_create(): + user_story = f.UserStoryFactory.create(tags=["tag"]) + project = Project.objects.get(id=user_story.project.id) + assert project.tags_colors == [["tag", None]] + + +def test_color_tags_project_fired_on_element_update(): + user_story = f.UserStoryFactory.create() + user_story.tags = ["tag"] + user_story.save() + project = Project.objects.get(id=user_story.project.id) + assert project.tags_colors == [["tag", None]] + + +def test_color_tags_project_fired_on_element_update_respecting_color(): + project = f.ProjectFactory.create(tags_colors=[["tag", "#123123"]]) + user_story = f.UserStoryFactory.create(project=project) + user_story.tags = ["tag"] + user_story.save() + project = Project.objects.get(id=user_story.project.id) + assert project.tags_colors == [["tag", "#123123"]] +>>>>>>> d64d158... WIP: migrations, removing automatic color generation