diff --git a/moped-database/metadata/tables.yaml b/moped-database/metadata/tables.yaml index 910775b33b..4bf378bcc3 100644 --- a/moped-database/metadata/tables.yaml +++ b/moped-database/metadata/tables.yaml @@ -998,79 +998,109 @@ - table: schema: public name: moped_proj_components + object_relationships: + - name: moped_components + using: + foreign_key_constraint_on: component_id + array_relationships: + - name: moped_proj_components_subcomponents + using: + foreign_key_constraint_on: + column: project_component_id + table: + schema: public + name: moped_proj_components_subcomponents + - name: moped_proj_features_components + using: + foreign_key_constraint_on: + column: moped_proj_component_id + table: + schema: public + name: moped_proj_features_components insert_permissions: - role: moped-admin permission: check: {} columns: - - project_component_id - - project_id - component_id - - name - description + - name + - project_component_id + - project_id + - status_id backend_only: false - role: moped-editor permission: check: {} columns: - - project_component_id - - project_id - component_id - - name - description + - name + - project_component_id + - project_id + - status_id backend_only: false select_permissions: - role: moped-admin permission: columns: - - project_component_id - - project_id - component_id - - name - description + - name + - project_component_id + - project_id + - status_id filter: {} - role: moped-editor permission: columns: - - project_component_id - - project_id - component_id - - name - description + - name + - project_component_id + - project_id + - status_id filter: {} - role: moped-viewer permission: columns: - - project_component_id - - project_id - component_id - - name - description + - name + - project_component_id + - project_id + - status_id filter: {} update_permissions: - role: moped-admin permission: columns: - - project_component_id - - project_id - component_id - - name - description + - name + - project_component_id + - project_id + - status_id filter: {} check: {} - role: moped-editor permission: columns: - - project_component_id - - project_id - component_id - - name - description + - name + - project_component_id + - project_id + - status_id filter: {} check: {} - table: schema: public name: moped_proj_components_subcomponents + object_relationships: + - name: moped_subcomponent + using: + foreign_key_constraint_on: subcomponent_id insert_permissions: - role: moped-admin permission: @@ -1078,6 +1108,7 @@ columns: - component_subcomponent_id - project_component_id + - status_id - subcomponent_id backend_only: false - role: moped-editor @@ -1086,6 +1117,7 @@ columns: - component_subcomponent_id - project_component_id + - status_id - subcomponent_id backend_only: false select_permissions: @@ -1094,6 +1126,7 @@ columns: - component_subcomponent_id - project_component_id + - status_id - subcomponent_id filter: {} - role: moped-editor @@ -1101,6 +1134,7 @@ columns: - component_subcomponent_id - project_component_id + - status_id - subcomponent_id filter: {} - role: moped-viewer @@ -1108,6 +1142,7 @@ columns: - component_subcomponent_id - project_component_id + - status_id - subcomponent_id filter: {} update_permissions: @@ -1116,6 +1151,7 @@ columns: - component_subcomponent_id - project_component_id + - status_id - subcomponent_id filter: {} check: {} @@ -1124,6 +1160,7 @@ columns: - component_subcomponent_id - project_component_id + - status_id - subcomponent_id filter: {} check: {} @@ -1416,6 +1453,14 @@ - name: moped_project using: foreign_key_constraint_on: project_id + array_relationships: + - name: moped_proj_features_components + using: + foreign_key_constraint_on: + column: moped_proj_features_id + table: + schema: public + name: moped_proj_features_components insert_permissions: - role: moped-admin permission: @@ -1482,6 +1527,21 @@ - table: schema: public name: moped_proj_features_components + object_relationships: + - name: moped_proj_component + using: + foreign_key_constraint_on: moped_proj_component_id + - name: moped_proj_feature + using: + manual_configuration: + remote_table: + schema: public + name: moped_proj_features + column_mapping: + moped_proj_features_id: feature_id + - name: moped_proj_feature_object + using: + foreign_key_constraint_on: moped_proj_features_id insert_permissions: - role: moped-admin permission: @@ -3666,6 +3726,13 @@ schema: public name: moped_project array_relationships: + - name: moped_proj_components + using: + foreign_key_constraint_on: + column: project_id + table: + schema: public + name: moped_proj_components - name: moped_proj_features using: foreign_key_constraint_on: @@ -4243,6 +4310,62 @@ - table: schema: public name: moped_subcomponents + insert_permissions: + - role: moped-admin + permission: + check: {} + columns: + - subcomponent_id + - subcomponent_name + - component_id + backend_only: false + - role: moped-editor + permission: + check: {} + columns: + - subcomponent_id + - subcomponent_name + - component_id + backend_only: false + select_permissions: + - role: moped-admin + permission: + columns: + - component_id + - subcomponent_id + - subcomponent_name + filter: {} + - role: moped-editor + permission: + columns: + - component_id + - subcomponent_id + - subcomponent_name + filter: {} + - role: moped-viewer + permission: + columns: + - component_id + - subcomponent_id + - subcomponent_name + filter: {} + update_permissions: + - role: moped-admin + permission: + columns: + - component_id + - subcomponent_id + - subcomponent_name + filter: {} + check: null + - role: moped-editor + permission: + columns: + - component_id + - subcomponent_id + - subcomponent_name + filter: {} + check: null - table: schema: public name: moped_subphases diff --git a/moped-database/migrations/1623029598102_alter_table_public_moped_components_alter_column_component_subtype/down.sql b/moped-database/migrations/1623029598102_alter_table_public_moped_components_alter_column_component_subtype/down.sql new file mode 100644 index 0000000000..c245616522 --- /dev/null +++ b/moped-database/migrations/1623029598102_alter_table_public_moped_components_alter_column_component_subtype/down.sql @@ -0,0 +1,5 @@ +UPDATE moped_components +SET component_subtype = ' ' +WHERE component_subtype IS NULL; + +ALTER TABLE "public"."moped_components" ALTER COLUMN "component_subtype" SET NOT NULL; diff --git a/moped-database/migrations/1623029598102_alter_table_public_moped_components_alter_column_component_subtype/up.sql b/moped-database/migrations/1623029598102_alter_table_public_moped_components_alter_column_component_subtype/up.sql new file mode 100644 index 0000000000..a5fb5f00e4 --- /dev/null +++ b/moped-database/migrations/1623029598102_alter_table_public_moped_components_alter_column_component_subtype/up.sql @@ -0,0 +1,5 @@ +ALTER TABLE "public"."moped_components" ALTER COLUMN "component_subtype" DROP NOT NULL; + +UPDATE moped_components + SET component_subtype = NULL + WHERE component_subtype = ' '; diff --git a/moped-database/migrations/1623342617906_alter_table_public_moped_proj_components_add_column_status_id/down.sql b/moped-database/migrations/1623342617906_alter_table_public_moped_proj_components_add_column_status_id/down.sql new file mode 100644 index 0000000000..f2b0ad652a --- /dev/null +++ b/moped-database/migrations/1623342617906_alter_table_public_moped_proj_components_add_column_status_id/down.sql @@ -0,0 +1 @@ +ALTER TABLE "public"."moped_proj_components" DROP COLUMN "status_id"; diff --git a/moped-database/migrations/1623342617906_alter_table_public_moped_proj_components_add_column_status_id/up.sql b/moped-database/migrations/1623342617906_alter_table_public_moped_proj_components_add_column_status_id/up.sql new file mode 100644 index 0000000000..9fed7fc2d4 --- /dev/null +++ b/moped-database/migrations/1623342617906_alter_table_public_moped_proj_components_add_column_status_id/up.sql @@ -0,0 +1 @@ +ALTER TABLE "public"."moped_proj_components" ADD COLUMN "status_id" integer NOT NULL DEFAULT 0; diff --git a/moped-database/migrations/1623343106518_alter_table_public_moped_proj_components_subcomponents_add_column_status_id/down.sql b/moped-database/migrations/1623343106518_alter_table_public_moped_proj_components_subcomponents_add_column_status_id/down.sql new file mode 100644 index 0000000000..ff24561114 --- /dev/null +++ b/moped-database/migrations/1623343106518_alter_table_public_moped_proj_components_subcomponents_add_column_status_id/down.sql @@ -0,0 +1 @@ +ALTER TABLE "public"."moped_proj_components_subcomponents" DROP COLUMN "status_id"; diff --git a/moped-database/migrations/1623343106518_alter_table_public_moped_proj_components_subcomponents_add_column_status_id/up.sql b/moped-database/migrations/1623343106518_alter_table_public_moped_proj_components_subcomponents_add_column_status_id/up.sql new file mode 100644 index 0000000000..22b0880c95 --- /dev/null +++ b/moped-database/migrations/1623343106518_alter_table_public_moped_proj_components_subcomponents_add_column_status_id/up.sql @@ -0,0 +1 @@ +ALTER TABLE "public"."moped_proj_components_subcomponents" ADD COLUMN "status_id" integer NOT NULL DEFAULT 0; diff --git a/moped-database/migrations/1624565634524_update_table_moped_components_add_generic_component/down.sql b/moped-database/migrations/1624565634524_update_table_moped_components_add_generic_component/down.sql new file mode 100644 index 0000000000..a60962038d --- /dev/null +++ b/moped-database/migrations/1624565634524_update_table_moped_components_add_generic_component/down.sql @@ -0,0 +1,6 @@ +/* Delete extent project component */ +DELETE FROM public.moped_components WHERE component_id = 0; + +/* Then delete the rest of the components in the migration */ +DELETE FROM public.moped_components + WHERE component_id >= 19 AND component_id <= 59; diff --git a/moped-database/migrations/1624565634524_update_table_moped_components_add_generic_component/up.sql b/moped-database/migrations/1624565634524_update_table_moped_components_add_generic_component/up.sql new file mode 100644 index 0000000000..9d4e6c9709 --- /dev/null +++ b/moped-database/migrations/1624565634524_update_table_moped_components_add_generic_component/up.sql @@ -0,0 +1,46 @@ +/* Adds the project extent component or generic type */ +INSERT INTO + public.moped_components (component_id, component_name, status_id, component_subtype, line_representation) + VALUES + (19, 'Bike Parking', 1, null, false), + (20, 'Bike Parking', 1, 'Corral', false), + (21, 'Bike Lane', 1, 'Turn Lane', true), + (22, 'Bike Lane', 1, 'Wide Curb Lane', true), + (23, 'Pavement Marking', 1, 'Crossbike', false), + (24, 'Access Control', 1, 'Driveway Gate', false), + (25, 'Dynamic Speed Display Device', 1, null, false), + (26, 'Guardrail', 1, null, true), + (27, 'Highway', 1, 'Access Ramp', false), + (28, 'Highway', 1, 'Added Capacity / Lanes', true), + (29, 'Highway', 1, 'Collector Distributor', true), + (30, 'Highway', 1, 'Flyover', true), + (31, 'Highway', 1, 'Intersection Grade Separation', true), + (32, 'Highway', 1, 'Managed Lane', true), + (33, 'Highway', 1, 'Toll Road', true), + (34, 'Landscaping', 1, null, true), + (35, 'Placemaking', 1, null, true), + (36, 'Refuge Island', 1, 'Bike', false), + (37, 'Refuge Island', 1, 'Ped', false), + (38, 'Refuge Island', 1, 'Bike/Ped', false), + (39, 'Signal', 1, 'School Zone Beacon', false), + (40, 'Pavement Marking', 1, 'School Zone', false), + (41, 'Sidewalk', 1, null, true), + (42, 'Sidewalk', 1, 'In Street', true), + (43, 'Sidewalk', 1, 'Wide', true), + (44, 'Sidewalk', 1, 'With Curb and Gutter', true), + (45, 'Sidewalk', 1, 'Rams', true), + (46, 'Speed Management', 1, 'Chicane', false), + (47, 'Speed Management', 1, 'Nbhd Traffic Circle', false), + (48, 'Speed Management', 1, 'Speed Cushions (Asphalt)', false), + (49, 'Speed Management', 1, 'Speed Cushions (Rubber)', false), + (50, 'Speed Management', 1, 'Speed Humps', false), + (51, 'Pavement Marking', 1, 'Stop Bar', false), + (52, 'Transit', 1, 'Lane', true), + (53, 'Transit', 1, 'Managed Lane Access Point', true), + (54, 'Transit', 1, 'Transit Queue Jump', true), + (55, 'Transit', 1, 'Transit/Bike Lane', true), + (56, 'Pavement Marking', 1, 'Two-stage Bike Turn Queue', false), + (57, 'Pavement Marking', 1, 'Sharrows', false), + (58, 'Pavement Marking', 1, 'Sharrows (Wide Curb Lane)', false), + (59, 'Signage', 1, null, false), + (0, 'Project Extent - Generic', 1, null, false); diff --git a/moped-database/seeds/1602292389297_initial_seed_staging.sql b/moped-database/seeds/1602292389297_initial_seed_staging.sql index 1d1b97e396..3761edbc5d 100644 --- a/moped-database/seeds/1602292389297_initial_seed_staging.sql +++ b/moped-database/seeds/1602292389297_initial_seed_staging.sql @@ -262,7 +262,7 @@ INSERT INTO public.moped_activity_log (activity_id, record_id, record_type, reco INSERT INTO public.moped_activity_log (activity_id, record_id, record_type, record_data, description, created_at, updated_by, record_project_id, operation_type) VALUES ('6ec15fe6-5080-4447-99f9-6e2fdc1a7c33', 187, 'moped_project', '{"id": "03b87f84-c895-4723-b6b2-482bec2346ec", "event": {"op": "INSERT", "data": {"new": {"added_by": null, "end_date": null, "date_added": "2021-03-23T01:36:54.42012+00:00", "eCapris_id": "1234", "is_retired": false, "project_id": 187, "start_date": "2021-03-22", "fiscal_year": "2017", "timeline_id": null, "project_name": "This is a test project", "project_uuid": "6fdfcab8-5149-4dea-8816-77a11ccf9c42", "current_phase": "construction", "project_order": null, "current_status": "canceled", "project_length": 0, "capitally_funded": true, "project_priority": "", "project_importance": null, "project_description": "This is a new test project", "project_extent_geojson": {"type": "FeatureCollection", "features": [{"id": 50194, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7268167026341, 30.28892818871917], [-97.72716572508216, 30.290033236814793], [-97.72719472646713, 30.290154684228455], [-97.72721484303474, 30.290250944586575], [-97.72722624242306, 30.29032737379613]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 150194}}]}, "project_description_public": null}, "old": null}, "trace_context": {"span_id": 1157060217750187198, "trace_id": 16547921757380504472}, "session_variables": {"x-hasura-role": "moped-admin", "x-hasura-user-id": "20c20b28-3b20-4e9e-a0bc-e1ef4e512f2c", "x-hasura-user-db-id": "7", "x-hasura-user-wg-id": "3"}}, "table": {"name": "moped_project", "schema": "public"}, "trigger": {"name": "activity_log_moped_project"}, "created_at": "2021-03-23T01:36:54.416039Z", "delivery_info": {"max_retries": 0, "current_retry": 0}}', '[]', '2021-03-23 01:37:05.743857+00', '20c20b28-3b20-4e9e-a0bc-e1ef4e512f2c', 187, 'INSERT'); INSERT INTO public.moped_activity_log (activity_id, record_id, record_type, record_data, description, created_at, updated_by, record_project_id, operation_type) VALUES ('41e05e8c-1d2b-45b3-934e-2184cba30c9e', 187, 'moped_project', '{"id": "8b5f0893-544f-46a1-ba5f-8803f9471b29", "event": {"op": "UPDATE", "data": {"new": {"added_by": null, "end_date": null, "date_added": "2021-03-23T01:36:54.42012+00:00", "eCapris_id": "1234", "is_retired": false, "project_id": 187, "start_date": "2021-03-22", "fiscal_year": "2017", "timeline_id": null, "project_name": "Checking on the project created log", "project_uuid": "6fdfcab8-5149-4dea-8816-77a11ccf9c42", "current_phase": "construction", "project_order": null, "current_status": "canceled", "project_length": 0, "capitally_funded": true, "project_priority": "", "project_importance": null, "project_description": "This is a new test project", "project_extent_geojson": {"type": "FeatureCollection", "features": [{"id": 50194, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7268167026341, 30.28892818871917], [-97.72716572508216, 30.290033236814793], [-97.72719472646713, 30.290154684228455], [-97.72721484303474, 30.290250944586575], [-97.72722624242306, 30.29032737379613]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 150194}}]}, "project_description_public": null}, "old": {"added_by": null, "end_date": null, "date_added": "2021-03-23T01:36:54.42012+00:00", "eCapris_id": "1234", "is_retired": false, "project_id": 187, "start_date": "2021-03-22", "fiscal_year": "2017", "timeline_id": null, "project_name": "This is a test project", "project_uuid": "6fdfcab8-5149-4dea-8816-77a11ccf9c42", "current_phase": "construction", "project_order": null, "current_status": "canceled", "project_length": 0, "capitally_funded": true, "project_priority": "", "project_importance": null, "project_description": "This is a new test project", "project_extent_geojson": {"type": "FeatureCollection", "features": [{"id": 50194, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7268167026341, 30.28892818871917], [-97.72716572508216, 30.290033236814793], [-97.72719472646713, 30.290154684228455], [-97.72721484303474, 30.290250944586575], [-97.72722624242306, 30.29032737379613]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 150194}}]}, "project_description_public": null}}, "trace_context": {"span_id": 5288847825067306386, "trace_id": 18346729627000287658}, "session_variables": {"x-hasura-role": "moped-admin", "x-hasura-user-id": "20c20b28-3b20-4e9e-a0bc-e1ef4e512f2c", "x-hasura-user-db-id": "7", "x-hasura-user-wg-id": "3"}}, "table": {"name": "moped_project", "schema": "public"}, "trigger": {"name": "activity_log_moped_project"}, "created_at": "2021-03-23T01:37:09.945328Z", "delivery_info": {"max_retries": 0, "current_retry": 0}}', '[{"new": "Checking on the project created log", "old": "This is a test project", "field": "project_name"}]', '2021-03-23 01:37:21.343012+00', '20c20b28-3b20-4e9e-a0bc-e1ef4e512f2c', 187, 'UPDATE'); INSERT INTO public.moped_activity_log (activity_id, record_id, record_type, record_data, description, created_at, updated_by, record_project_id, operation_type) VALUES ('66a57080-70cf-419a-b5e9-f8ed4dc080ed', 188, 'moped_project', '{"id": "8031dfb9-45a2-4f26-a751-811cc2831820", "event": {"op": "INSERT", "data": {"new": {"added_by": null, "end_date": null, "date_added": "2021-03-23T16:56:35.754826+00:00", "eCapris_id": "", "is_retired": false, "project_id": 188, "start_date": "2021-03-23", "fiscal_year": "2021", "timeline_id": null, "project_name": "Another John Project", "project_uuid": "892a57ab-74d1-4033-8920-4084d6117c41", "current_phase": "complete", "project_order": null, "current_status": "active", "project_length": 0, "capitally_funded": false, "project_priority": "", "project_importance": null, "project_description": "This is gonna be good", "project_extent_geojson": {"type": "FeatureCollection", "features": [{"id": 54325, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74485321715474, 30.269660368310596], [-97.74449598044157, 30.270580023866273]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154325}}, {"id": 54442, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74521112442017, 30.268721592638997], [-97.74485321715474, 30.269660368310596]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154442}}, {"id": 54443, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74521112442017, 30.268721592638997], [-97.7462675794959, 30.269028968820365]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154443}}, {"id": 48417, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7462675794959, 30.269028968820365], [-97.74594135582447, 30.269968465464004]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 148417}}, {"id": 54312, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7441355586052, 30.26842203386127], [-97.74378100410104, 30.269352704539784]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154312}}, {"id": 47473, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74247946217656, 30.26897163447613], [-97.74378100410104, 30.269352704539784]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147473}}, {"id": 53749, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74282043799758, 30.26807093069874], [-97.7441355586052, 30.26842203386127]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 153749}}, {"id": 53748, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74282043799758, 30.26807093069874], [-97.74247946217656, 30.26897163447613]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 153748}}, {"id": 52382, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74111723527312, 30.268608516185054], [-97.74078816175461, 30.269537013352178]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 152382}}, {"id": 54322, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74146189913154, 30.267688697374666], [-97.74111723527312, 30.268608516185054]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154322}}, {"id": 47852, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74111723527312, 30.268608516185054], [-97.74004502221942, 30.268300849117793]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147852}}, {"id": 48531, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7403829805553, 30.267399994400208], [-97.74004502221942, 30.268300849117793]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 148531}}, {"id": 51717, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73934295400977, 30.267073646083887], [-97.73895654827356, 30.26801200315832]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 151717}}, {"id": 47863, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73895654827356, 30.26801200315832], [-97.7379254065454, 30.2677050581497]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147863}}, {"id": 6518, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73825431242585, 30.26678754746615], [-97.7379254065454, 30.2677050581497]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 106518}}, {"id": 47866, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74343013763428, 30.270261214776838], [-97.74450302124023, 30.270594791564676]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147866}}]}, "project_description_public": null}, "old": null}, "trace_context": {"span_id": 12892454171637769490, "trace_id": 12471440775515417897}, "session_variables": {"x-hasura-role": "moped-admin", "x-hasura-user-id": "40422ee4-4aca-43b3-898c-ed2ad2f88f6d", "x-hasura-user-db-id": "10", "x-hasura-user-wg-id": "3"}}, "table": {"name": "moped_project", "schema": "public"}, "trigger": {"name": "activity_log_moped_project"}, "created_at": "2021-03-23T16:56:35.749854Z", "delivery_info": {"max_retries": 0, "current_retry": 0}}', '[]', '2021-03-23 16:56:48.283344+00', '40422ee4-4aca-43b3-898c-ed2ad2f88f6d', 188, 'INSERT'); -INSERT INTO public.moped_activity_log (activity_id, record_id, record_type, record_data, description, created_at, updated_by, record_project_id, operation_type) VALUES ('aed04c89-dc0e-469a-99d2-e0bfc184c9fa', 159, 'moped_project', '{"id": "b28c5a6e-5a54-4bda-91db-12b227cedbd4", "event": {"op": "UPDATE", "data": {"new": {"added_by": null, "end_date": null, "date_added": "2021-01-26T10:06:41.4938+00:00", "eCapris_id": "5771.072", "is_retired": false, "project_id": 159, "start_date": "2021-03-23", "fiscal_year": "2018", "timeline_id": null, "project_name": "Avenue G Healthy Street", "project_uuid": "f95c4c47-6099-49a2-9513-376c34459cf8", "current_phase": "construction", "project_order": null, "current_status": "potential", "project_length": 106, "capitally_funded": true, "project_priority": "Low", "project_importance": null, "project_description": "Installation of semi-permanent traffic barriers based on community demand", "project_extent_geojson": {"type": "FeatureCollection", "features": [{"id": 38528, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.73157846182585, 30.299585000265452], [-97.73083079606295, 30.300865204565497]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 138528}}, {"id": 2479, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.73083079606295, 30.300865204565497], [-97.73008245974779, 30.302170141796154]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 102479}}, {"id": 23584, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.73008245974779, 30.302170141796154], [-97.72937687113881, 30.303294434922478]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 123584}}, {"id": 25049, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72937687113881, 30.303294434922478], [-97.72867178544402, 30.304407715565787]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 125049}}, {"id": 29536, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72867178544402, 30.304407715565787], [-97.72792797535658, 30.30553675911436]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 129536}}, {"id": 39707, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72792797535658, 30.30553675911436], [-97.72720964625478, 30.306666368571754]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 139707}}, {"id": 23598, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72720964625478, 30.306666368571754], [-97.72683581337333, 30.307250055327543], [-97.72650389000773, 30.307713615628003]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 123598}}, {"id": 6478, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72650389000773, 30.307713615628003], [-97.72520100697875, 30.309708651094596]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 106478}}, {"id": 28790, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72464444860816, 30.31059667123114], [-97.72388169541955, 30.311755600662806]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 128790}}, {"id": 27487, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72388169541955, 30.311755600662806], [-97.72316671907902, 30.312876889471113]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 127487}}, {"id": 37217, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72520100697875, 30.309708651094596], [-97.72464444860816, 30.31059667123114]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 137217}}, {"id": 27815, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72316671907902, 30.312876889471113], [-97.72241871803999, 30.31403883100012]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 127815}}, {"id": 34837, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72241871803999, 30.31403883100012], [-97.7217498421669, 30.31512261234316]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 134837}}, {"id": 33035, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.7217498421669, 30.31512261234316], [-97.72102111950517, 30.316191476153534]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 133035}}, {"id": 25270, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72102111950517, 30.316191476153534], [-97.72029139101505, 30.317271181739386]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 125270}}, {"id": 25101, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72029139101505, 30.317271181739386], [-97.71960692480206, 30.318340890377684]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 125101}}, {"id": 24260, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.71960692480206, 30.318340890377684], [-97.71889278665185, 30.319423755882283]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 124260}}, {"id": 33003, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.71889278665185, 30.319423755882283], [-97.7181132696569, 30.32063728036431]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 133003}}]}, "project_description_public": null}, "old": {"added_by": null, "end_date": null, "date_added": "2021-01-26T10:06:41.4938+00:00", "eCapris_id": "5771.072", "is_retired": false, "project_id": 159, "start_date": "2021-03-23", "fiscal_year": "2018", "timeline_id": null, "project_name": "Avenue G Healthy Street", "project_uuid": "f95c4c47-6099-49a2-9513-376c34459cf8", "current_phase": "construction", "project_order": null, "current_status": "active", "project_length": 106, "capitally_funded": true, "project_priority": "Low", "project_importance": null, "project_description": "Installation of semi-permanent traffic barriers based on community demand", "project_extent_geojson": {"type": "FeatureCollection", "features": [{"id": 38528, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.73157846182585, 30.299585000265452], [-97.73083079606295, 30.300865204565497]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 138528}}, {"id": 2479, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.73083079606295, 30.300865204565497], [-97.73008245974779, 30.302170141796154]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 102479}}, {"id": 23584, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.73008245974779, 30.302170141796154], [-97.72937687113881, 30.303294434922478]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 123584}}, {"id": 25049, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72937687113881, 30.303294434922478], [-97.72867178544402, 30.304407715565787]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 125049}}, {"id": 29536, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72867178544402, 30.304407715565787], [-97.72792797535658, 30.30553675911436]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 129536}}, {"id": 39707, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72792797535658, 30.30553675911436], [-97.72720964625478, 30.306666368571754]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 139707}}, {"id": 23598, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72720964625478, 30.306666368571754], [-97.72683581337333, 30.307250055327543], [-97.72650389000773, 30.307713615628003]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 123598}}, {"id": 6478, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72650389000773, 30.307713615628003], [-97.72520100697875, 30.309708651094596]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 106478}}, {"id": 28790, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72464444860816, 30.31059667123114], [-97.72388169541955, 30.311755600662806]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 128790}}, {"id": 27487, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72388169541955, 30.311755600662806], [-97.72316671907902, 30.312876889471113]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 127487}}, {"id": 37217, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72520100697875, 30.309708651094596], [-97.72464444860816, 30.31059667123114]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 137217}}, {"id": 27815, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72316671907902, 30.312876889471113], [-97.72241871803999, 30.31403883100012]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 127815}}, {"id": 34837, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72241871803999, 30.31403883100012], [-97.7217498421669, 30.31512261234316]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 134837}}, {"id": 33035, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.7217498421669, 30.31512261234316], [-97.72102111950517, 30.316191476153534]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 133035}}, {"id": 25270, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72102111950517, 30.316191476153534], [-97.72029139101505, 30.317271181739386]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 125270}}, {"id": 25101, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72029139101505, 30.317271181739386], [-97.71960692480206, 30.318340890377684]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 125101}}, {"id": 24260, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.71960692480206, 30.318340890377684], [-97.71889278665185, 30.319423755882283]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 124260}}, {"id": 33003, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.71889278665185, 30.319423755882283], [-97.7181132696569, 30.32063728036431]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 133003}}]}, "project_description_public": null}}, "trace_context": {"span_id": 17784305494158522556, "trace_id": 578298752847253035}, "session_variables": {"x-hasura-role": "moped-admin", "x-hasura-user-id": "ec609228-4442-41e5-a0dc-2c417af00c6b", "x-hasura-user-db-id": "1", "x-hasura-user-wg-id": "18"}}, "table": {"name": "moped_project", "schema": "public"}, "trigger": {"name": "activity_log_moped_project"}, "created_at": "2021-03-23T16:56:56.885225Z", "delivery_info": {"max_retries": 0, "current_retry": 0}}', '[{"new": "potential", "old": "active", "field": "current_status"}]', '2021-03-23 16:57:08.622478+00', 'ec609228-4442-41e5-a0dc-2c417af00c6b', 159, 'UPDATE'); +INSERT INTO public.moped_activity_log (activity_id, record_id, record_type, record_data, description, created_at, updated_by, record_project_id, operation_type) VALUES ('aed04c89-dc0e-469a-99d2-e0bfc184c9fa', 159, 'moped_project', '{"id": "b28c5a6e-5a54-4bda-91db-12b227cedbd4", "event": {"op": "UPDATE", "data": {"new": {"added_by": null, "end_date": null, "date_added": "2021-01-26T10:06:41.4938+00:00", "eCapris_id": "5771.072", "is_retired": false, "project_id": 159, "start_date": "2021-03-23", "fiscal_year": "2018", "timeline_id": null, "project_name": "Downtown 6th & Congress", "project_uuid": "f95c4c47-6099-49a2-9513-376c34459cf8", "current_phase": "construction", "project_order": null, "current_status": "potential", "project_length": 106, "capitally_funded": true, "project_priority": "Low", "project_importance": null, "project_description": "Installation of semi-permanent traffic barriers based on community demand", "project_extent_geojson": {"type": "FeatureCollection", "features": [{"id": 38528, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.73157846182585, 30.299585000265452], [-97.73083079606295, 30.300865204565497]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 138528}}, {"id": 2479, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.73083079606295, 30.300865204565497], [-97.73008245974779, 30.302170141796154]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 102479}}, {"id": 23584, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.73008245974779, 30.302170141796154], [-97.72937687113881, 30.303294434922478]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 123584}}, {"id": 25049, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72937687113881, 30.303294434922478], [-97.72867178544402, 30.304407715565787]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 125049}}, {"id": 29536, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72867178544402, 30.304407715565787], [-97.72792797535658, 30.30553675911436]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 129536}}, {"id": 39707, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72792797535658, 30.30553675911436], [-97.72720964625478, 30.306666368571754]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 139707}}, {"id": 23598, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72720964625478, 30.306666368571754], [-97.72683581337333, 30.307250055327543], [-97.72650389000773, 30.307713615628003]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 123598}}, {"id": 6478, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72650389000773, 30.307713615628003], [-97.72520100697875, 30.309708651094596]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 106478}}, {"id": 28790, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72464444860816, 30.31059667123114], [-97.72388169541955, 30.311755600662806]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 128790}}, {"id": 27487, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72388169541955, 30.311755600662806], [-97.72316671907902, 30.312876889471113]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 127487}}, {"id": 37217, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72520100697875, 30.309708651094596], [-97.72464444860816, 30.31059667123114]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 137217}}, {"id": 27815, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72316671907902, 30.312876889471113], [-97.72241871803999, 30.31403883100012]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 127815}}, {"id": 34837, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72241871803999, 30.31403883100012], [-97.7217498421669, 30.31512261234316]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 134837}}, {"id": 33035, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.7217498421669, 30.31512261234316], [-97.72102111950517, 30.316191476153534]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 133035}}, {"id": 25270, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72102111950517, 30.316191476153534], [-97.72029139101505, 30.317271181739386]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 125270}}, {"id": 25101, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72029139101505, 30.317271181739386], [-97.71960692480206, 30.318340890377684]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 125101}}, {"id": 24260, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.71960692480206, 30.318340890377684], [-97.71889278665185, 30.319423755882283]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 124260}}, {"id": 33003, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.71889278665185, 30.319423755882283], [-97.7181132696569, 30.32063728036431]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 133003}}]}, "project_description_public": null}, "old": {"added_by": null, "end_date": null, "date_added": "2021-01-26T10:06:41.4938+00:00", "eCapris_id": "5771.072", "is_retired": false, "project_id": 159, "start_date": "2021-03-23", "fiscal_year": "2018", "timeline_id": null, "project_name": "Avenue G Healthy Street", "project_uuid": "f95c4c47-6099-49a2-9513-376c34459cf8", "current_phase": "construction", "project_order": null, "current_status": "active", "project_length": 106, "capitally_funded": true, "project_priority": "Low", "project_importance": null, "project_description": "Installation of semi-permanent traffic barriers based on community demand", "project_extent_geojson": {"type": "FeatureCollection", "features": [{"id": 38528, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.73157846182585, 30.299585000265452], [-97.73083079606295, 30.300865204565497]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 138528}}, {"id": 2479, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.73083079606295, 30.300865204565497], [-97.73008245974779, 30.302170141796154]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 102479}}, {"id": 23584, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.73008245974779, 30.302170141796154], [-97.72937687113881, 30.303294434922478]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 123584}}, {"id": 25049, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72937687113881, 30.303294434922478], [-97.72867178544402, 30.304407715565787]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 125049}}, {"id": 29536, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72867178544402, 30.304407715565787], [-97.72792797535658, 30.30553675911436]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 129536}}, {"id": 39707, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72792797535658, 30.30553675911436], [-97.72720964625478, 30.306666368571754]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 139707}}, {"id": 23598, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72720964625478, 30.306666368571754], [-97.72683581337333, 30.307250055327543], [-97.72650389000773, 30.307713615628003]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 123598}}, {"id": 6478, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72650389000773, 30.307713615628003], [-97.72520100697875, 30.309708651094596]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 106478}}, {"id": 28790, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72464444860816, 30.31059667123114], [-97.72388169541955, 30.311755600662806]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 128790}}, {"id": 27487, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72388169541955, 30.311755600662806], [-97.72316671907902, 30.312876889471113]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 127487}}, {"id": 37217, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72520100697875, 30.309708651094596], [-97.72464444860816, 30.31059667123114]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 137217}}, {"id": 27815, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72316671907902, 30.312876889471113], [-97.72241871803999, 30.31403883100012]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 127815}}, {"id": 34837, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72241871803999, 30.31403883100012], [-97.7217498421669, 30.31512261234316]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 134837}}, {"id": 33035, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.7217498421669, 30.31512261234316], [-97.72102111950517, 30.316191476153534]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 133035}}, {"id": 25270, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72102111950517, 30.316191476153534], [-97.72029139101505, 30.317271181739386]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 125270}}, {"id": 25101, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72029139101505, 30.317271181739386], [-97.71960692480206, 30.318340890377684]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 125101}}, {"id": 24260, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.71960692480206, 30.318340890377684], [-97.71889278665185, 30.319423755882283]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 124260}}, {"id": 33003, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.71889278665185, 30.319423755882283], [-97.7181132696569, 30.32063728036431]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 133003}}]}, "project_description_public": null}}, "trace_context": {"span_id": 17784305494158522556, "trace_id": 578298752847253035}, "session_variables": {"x-hasura-role": "moped-admin", "x-hasura-user-id": "ec609228-4442-41e5-a0dc-2c417af00c6b", "x-hasura-user-db-id": "1", "x-hasura-user-wg-id": "18"}}, "table": {"name": "moped_project", "schema": "public"}, "trigger": {"name": "activity_log_moped_project"}, "created_at": "2021-03-23T16:56:56.885225Z", "delivery_info": {"max_retries": 0, "current_retry": 0}}', '[{"new": "potential", "old": "active", "field": "current_status"}]', '2021-03-23 16:57:08.622478+00', 'ec609228-4442-41e5-a0dc-2c417af00c6b', 159, 'UPDATE'); INSERT INTO public.moped_activity_log (activity_id, record_id, record_type, record_data, description, created_at, updated_by, record_project_id, operation_type) VALUES ('b79b12c5-1dc3-47c7-b962-4bc43f924f30', 159, 'moped_project', '{"id": "9dbc083a-d82c-4bdc-ad0d-3b486d404595", "event": {"op": "UPDATE", "data": {"new": {"added_by": null, "end_date": null, "date_added": "2021-01-26T10:06:41.4938+00:00", "eCapris_id": "5771.072", "is_retired": false, "project_id": 159, "start_date": "2021-03-23", "fiscal_year": "2018", "timeline_id": null, "project_name": "Avenue G Healthy Street", "project_uuid": "f95c4c47-6099-49a2-9513-376c34459cf8", "current_phase": "potential", "project_order": null, "current_status": "potential", "project_length": 106, "capitally_funded": true, "project_priority": "Low", "project_importance": null, "project_description": "Installation of semi-permanent traffic barriers based on community demand", "project_extent_geojson": {"type": "FeatureCollection", "features": [{"id": 38528, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.73157846182585, 30.299585000265452], [-97.73083079606295, 30.300865204565497]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 138528}}, {"id": 2479, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.73083079606295, 30.300865204565497], [-97.73008245974779, 30.302170141796154]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 102479}}, {"id": 23584, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.73008245974779, 30.302170141796154], [-97.72937687113881, 30.303294434922478]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 123584}}, {"id": 25049, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72937687113881, 30.303294434922478], [-97.72867178544402, 30.304407715565787]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 125049}}, {"id": 29536, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72867178544402, 30.304407715565787], [-97.72792797535658, 30.30553675911436]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 129536}}, {"id": 39707, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72792797535658, 30.30553675911436], [-97.72720964625478, 30.306666368571754]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 139707}}, {"id": 23598, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72720964625478, 30.306666368571754], [-97.72683581337333, 30.307250055327543], [-97.72650389000773, 30.307713615628003]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 123598}}, {"id": 6478, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72650389000773, 30.307713615628003], [-97.72520100697875, 30.309708651094596]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 106478}}, {"id": 28790, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72464444860816, 30.31059667123114], [-97.72388169541955, 30.311755600662806]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 128790}}, {"id": 27487, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72388169541955, 30.311755600662806], [-97.72316671907902, 30.312876889471113]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 127487}}, {"id": 37217, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72520100697875, 30.309708651094596], [-97.72464444860816, 30.31059667123114]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 137217}}, {"id": 27815, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72316671907902, 30.312876889471113], [-97.72241871803999, 30.31403883100012]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 127815}}, {"id": 34837, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72241871803999, 30.31403883100012], [-97.7217498421669, 30.31512261234316]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 134837}}, {"id": 33035, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.7217498421669, 30.31512261234316], [-97.72102111950517, 30.316191476153534]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 133035}}, {"id": 25270, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72102111950517, 30.316191476153534], [-97.72029139101505, 30.317271181739386]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 125270}}, {"id": 25101, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72029139101505, 30.317271181739386], [-97.71960692480206, 30.318340890377684]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 125101}}, {"id": 24260, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.71960692480206, 30.318340890377684], [-97.71889278665185, 30.319423755882283]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 124260}}, {"id": 33003, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.71889278665185, 30.319423755882283], [-97.7181132696569, 30.32063728036431]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 133003}}]}, "project_description_public": null}, "old": {"added_by": null, "end_date": null, "date_added": "2021-01-26T10:06:41.4938+00:00", "eCapris_id": "5771.072", "is_retired": false, "project_id": 159, "start_date": "2021-03-23", "fiscal_year": "2018", "timeline_id": null, "project_name": "Avenue G Healthy Street", "project_uuid": "f95c4c47-6099-49a2-9513-376c34459cf8", "current_phase": "construction", "project_order": null, "current_status": "potential", "project_length": 106, "capitally_funded": true, "project_priority": "Low", "project_importance": null, "project_description": "Installation of semi-permanent traffic barriers based on community demand", "project_extent_geojson": {"type": "FeatureCollection", "features": [{"id": 38528, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.73157846182585, 30.299585000265452], [-97.73083079606295, 30.300865204565497]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 138528}}, {"id": 2479, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.73083079606295, 30.300865204565497], [-97.73008245974779, 30.302170141796154]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 102479}}, {"id": 23584, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.73008245974779, 30.302170141796154], [-97.72937687113881, 30.303294434922478]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 123584}}, {"id": 25049, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72937687113881, 30.303294434922478], [-97.72867178544402, 30.304407715565787]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 125049}}, {"id": 29536, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72867178544402, 30.304407715565787], [-97.72792797535658, 30.30553675911436]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 129536}}, {"id": 39707, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72792797535658, 30.30553675911436], [-97.72720964625478, 30.306666368571754]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 139707}}, {"id": 23598, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72720964625478, 30.306666368571754], [-97.72683581337333, 30.307250055327543], [-97.72650389000773, 30.307713615628003]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 123598}}, {"id": 6478, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72650389000773, 30.307713615628003], [-97.72520100697875, 30.309708651094596]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 106478}}, {"id": 28790, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72464444860816, 30.31059667123114], [-97.72388169541955, 30.311755600662806]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 128790}}, {"id": 27487, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72388169541955, 30.311755600662806], [-97.72316671907902, 30.312876889471113]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 127487}}, {"id": 37217, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72520100697875, 30.309708651094596], [-97.72464444860816, 30.31059667123114]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 137217}}, {"id": 27815, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72316671907902, 30.312876889471113], [-97.72241871803999, 30.31403883100012]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 127815}}, {"id": 34837, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72241871803999, 30.31403883100012], [-97.7217498421669, 30.31512261234316]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 134837}}, {"id": 33035, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.7217498421669, 30.31512261234316], [-97.72102111950517, 30.316191476153534]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 133035}}, {"id": 25270, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72102111950517, 30.316191476153534], [-97.72029139101505, 30.317271181739386]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 125270}}, {"id": 25101, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.72029139101505, 30.317271181739386], [-97.71960692480206, 30.318340890377684]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 125101}}, {"id": 24260, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.71960692480206, 30.318340890377684], [-97.71889278665185, 30.319423755882283]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 124260}}, {"id": 33003, "type": "Feature", "source": "jsx-source-0", "geometry": {"type": "LineString", "coordinates": [[-97.71889278665185, 30.319423755882283], [-97.7181132696569, 30.32063728036431]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 133003}}]}, "project_description_public": null}}, "trace_context": {"span_id": 6607851743559513518, "trace_id": 17961708981458787052}, "session_variables": {"x-hasura-role": "moped-admin", "x-hasura-user-id": "ec609228-4442-41e5-a0dc-2c417af00c6b", "x-hasura-user-db-id": "1", "x-hasura-user-wg-id": "18"}}, "table": {"name": "moped_project", "schema": "public"}, "trigger": {"name": "activity_log_moped_project"}, "created_at": "2021-03-23T16:57:03.698854Z", "delivery_info": {"max_retries": 0, "current_retry": 0}}', '[{"new": "potential", "old": "construction", "field": "current_phase"}]', '2021-03-23 16:57:15.084635+00', 'ec609228-4442-41e5-a0dc-2c417af00c6b', 159, 'UPDATE'); INSERT INTO public.moped_activity_log (activity_id, record_id, record_type, record_data, description, created_at, updated_by, record_project_id, operation_type) VALUES ('14578c20-435f-4e72-a5a4-c33067a22e7d', 188, 'moped_project', '{"id": "49cd6ef0-0737-495f-9f45-36171acb063d", "event": {"op": "UPDATE", "data": {"new": {"added_by": null, "end_date": null, "date_added": "2021-03-23T16:56:35.754826+00:00", "eCapris_id": "", "is_retired": false, "project_id": 188, "start_date": "2021-03-23", "fiscal_year": "2021", "timeline_id": null, "project_name": "Another John Project", "project_uuid": "892a57ab-74d1-4033-8920-4084d6117c41", "current_phase": "complete", "project_order": null, "current_status": "active", "project_length": 0, "capitally_funded": false, "project_priority": "", "project_importance": null, "project_description": "This is gonna be good", "project_extent_geojson": {"type": "FeatureCollection", "features": [{"id": 54325, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74485321715474, 30.269660368310596], [-97.74449598044157, 30.270580023866273]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154325}}, {"id": 54442, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74521112442017, 30.268721592638997], [-97.74485321715474, 30.269660368310596]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154442}}, {"id": 54443, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74521112442017, 30.268721592638997], [-97.7462675794959, 30.269028968820365]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154443}}, {"id": 48417, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7462675794959, 30.269028968820365], [-97.74594135582447, 30.269968465464004]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 148417}}, {"id": 54312, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7441355586052, 30.26842203386127], [-97.74378100410104, 30.269352704539784]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154312}}, {"id": 47473, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74247946217656, 30.26897163447613], [-97.74378100410104, 30.269352704539784]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147473}}, {"id": 53749, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74282043799758, 30.26807093069874], [-97.7441355586052, 30.26842203386127]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 153749}}, {"id": 53748, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74282043799758, 30.26807093069874], [-97.74247946217656, 30.26897163447613]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 153748}}, {"id": 52382, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74111723527312, 30.268608516185054], [-97.74078816175461, 30.269537013352178]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 152382}}, {"id": 54322, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74146189913154, 30.267688697374666], [-97.74111723527312, 30.268608516185054]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154322}}, {"id": 47852, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74111723527312, 30.268608516185054], [-97.74004502221942, 30.268300849117793]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147852}}, {"id": 48531, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7403829805553, 30.267399994400208], [-97.74004502221942, 30.268300849117793]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 148531}}, {"id": 51717, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73934295400977, 30.267073646083887], [-97.73895654827356, 30.26801200315832]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 151717}}, {"id": 47863, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73895654827356, 30.26801200315832], [-97.7379254065454, 30.2677050581497]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147863}}, {"id": 6518, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73825431242585, 30.26678754746615], [-97.7379254065454, 30.2677050581497]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 106518}}, {"id": 47866, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74343013763428, 30.270261214776838], [-97.74450302124023, 30.270594791564676]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147866}}, {"id": 48693, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74449598044157, 30.270580023866273], [-97.74560004472733, 30.27087740434517]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 148693}}]}, "project_description_public": null}, "old": {"added_by": null, "end_date": null, "date_added": "2021-03-23T16:56:35.754826+00:00", "eCapris_id": "", "is_retired": false, "project_id": 188, "start_date": "2021-03-23", "fiscal_year": "2021", "timeline_id": null, "project_name": "Another John Project", "project_uuid": "892a57ab-74d1-4033-8920-4084d6117c41", "current_phase": "complete", "project_order": null, "current_status": "active", "project_length": 0, "capitally_funded": false, "project_priority": "", "project_importance": null, "project_description": "This is gonna be good", "project_extent_geojson": {"type": "FeatureCollection", "features": [{"id": 54325, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74485321715474, 30.269660368310596], [-97.74449598044157, 30.270580023866273]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154325}}, {"id": 54442, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74521112442017, 30.268721592638997], [-97.74485321715474, 30.269660368310596]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154442}}, {"id": 54443, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74521112442017, 30.268721592638997], [-97.7462675794959, 30.269028968820365]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154443}}, {"id": 48417, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7462675794959, 30.269028968820365], [-97.74594135582447, 30.269968465464004]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 148417}}, {"id": 54312, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7441355586052, 30.26842203386127], [-97.74378100410104, 30.269352704539784]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154312}}, {"id": 47473, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74247946217656, 30.26897163447613], [-97.74378100410104, 30.269352704539784]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147473}}, {"id": 53749, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74282043799758, 30.26807093069874], [-97.7441355586052, 30.26842203386127]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 153749}}, {"id": 53748, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74282043799758, 30.26807093069874], [-97.74247946217656, 30.26897163447613]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 153748}}, {"id": 52382, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74111723527312, 30.268608516185054], [-97.74078816175461, 30.269537013352178]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 152382}}, {"id": 54322, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74146189913154, 30.267688697374666], [-97.74111723527312, 30.268608516185054]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154322}}, {"id": 47852, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74111723527312, 30.268608516185054], [-97.74004502221942, 30.268300849117793]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147852}}, {"id": 48531, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7403829805553, 30.267399994400208], [-97.74004502221942, 30.268300849117793]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 148531}}, {"id": 51717, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73934295400977, 30.267073646083887], [-97.73895654827356, 30.26801200315832]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 151717}}, {"id": 47863, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73895654827356, 30.26801200315832], [-97.7379254065454, 30.2677050581497]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147863}}, {"id": 6518, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73825431242585, 30.26678754746615], [-97.7379254065454, 30.2677050581497]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 106518}}, {"id": 47866, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74343013763428, 30.270261214776838], [-97.74450302124023, 30.270594791564676]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147866}}]}, "project_description_public": null}}, "trace_context": {"span_id": 7314644711510233905, "trace_id": 14481523999612599550}, "session_variables": {"x-hasura-role": "moped-admin", "x-hasura-user-id": "40422ee4-4aca-43b3-898c-ed2ad2f88f6d", "x-hasura-user-db-id": "10", "x-hasura-user-wg-id": "3"}}, "table": {"name": "moped_project", "schema": "public"}, "trigger": {"name": "activity_log_moped_project"}, "created_at": "2021-03-23T16:57:37.740995Z", "delivery_info": {"max_retries": 0, "current_retry": 0}}', '[{"new": {"CTN": [154325, 154442, 154443, 148417, 154312, 147473, 153749, 153748, 152382, 154322, 147852, 148531, 151717, 147863, 106518, 148693, 147866]}, "old": {"CTN": [154325, 154442, 154443, 148417, 154312, 147473, 153749, 153748, 152382, 154322, 147852, 148531, 151717, 147863, 106518]}, "field": "project_extent_ids"}, {"new": {"type": "FeatureCollection", "features": [{"id": 54325, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74485321715474, 30.269660368310596], [-97.74449598044157, 30.270580023866273]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154325}}, {"id": 54442, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74521112442017, 30.268721592638997], [-97.74485321715474, 30.269660368310596]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154442}}, {"id": 54443, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74521112442017, 30.268721592638997], [-97.7462675794959, 30.269028968820365]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154443}}, {"id": 48417, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7462675794959, 30.269028968820365], [-97.74594135582447, 30.269968465464004]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 148417}}, {"id": 54312, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7441355586052, 30.26842203386127], [-97.74378100410104, 30.269352704539784]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154312}}, {"id": 47473, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74247946217656, 30.26897163447613], [-97.74378100410104, 30.269352704539784]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147473}}, {"id": 53749, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74282043799758, 30.26807093069874], [-97.7441355586052, 30.26842203386127]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 153749}}, {"id": 53748, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74282043799758, 30.26807093069874], [-97.74247946217656, 30.26897163447613]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 153748}}, {"id": 52382, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74111723527312, 30.268608516185054], [-97.74078816175461, 30.269537013352178]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 152382}}, {"id": 54322, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74146189913154, 30.267688697374666], [-97.74111723527312, 30.268608516185054]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154322}}, {"id": 47852, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74111723527312, 30.268608516185054], [-97.74004502221942, 30.268300849117793]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147852}}, {"id": 48531, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7403829805553, 30.267399994400208], [-97.74004502221942, 30.268300849117793]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 148531}}, {"id": 51717, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73934295400977, 30.267073646083887], [-97.73895654827356, 30.26801200315832]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 151717}}, {"id": 47863, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73895654827356, 30.26801200315832], [-97.7379254065454, 30.2677050581497]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147863}}, {"id": 6518, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73825431242585, 30.26678754746615], [-97.7379254065454, 30.2677050581497]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 106518}}, {"id": 47866, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74343013763428, 30.270261214776838], [-97.74450302124023, 30.270594791564676]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147866}}, {"id": 48693, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74449598044157, 30.270580023866273], [-97.74560004472733, 30.27087740434517]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 148693}}]}, "old": {"type": "FeatureCollection", "features": [{"id": 54325, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74485321715474, 30.269660368310596], [-97.74449598044157, 30.270580023866273]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154325}}, {"id": 54442, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74521112442017, 30.268721592638997], [-97.74485321715474, 30.269660368310596]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154442}}, {"id": 54443, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74521112442017, 30.268721592638997], [-97.7462675794959, 30.269028968820365]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154443}}, {"id": 48417, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7462675794959, 30.269028968820365], [-97.74594135582447, 30.269968465464004]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 148417}}, {"id": 54312, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7441355586052, 30.26842203386127], [-97.74378100410104, 30.269352704539784]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154312}}, {"id": 47473, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74247946217656, 30.26897163447613], [-97.74378100410104, 30.269352704539784]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147473}}, {"id": 53749, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74282043799758, 30.26807093069874], [-97.7441355586052, 30.26842203386127]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 153749}}, {"id": 53748, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74282043799758, 30.26807093069874], [-97.74247946217656, 30.26897163447613]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 153748}}, {"id": 52382, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74111723527312, 30.268608516185054], [-97.74078816175461, 30.269537013352178]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 152382}}, {"id": 54322, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74146189913154, 30.267688697374666], [-97.74111723527312, 30.268608516185054]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154322}}, {"id": 47852, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74111723527312, 30.268608516185054], [-97.74004502221942, 30.268300849117793]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147852}}, {"id": 48531, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7403829805553, 30.267399994400208], [-97.74004502221942, 30.268300849117793]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 148531}}, {"id": 51717, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73934295400977, 30.267073646083887], [-97.73895654827356, 30.26801200315832]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 151717}}, {"id": 47863, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73895654827356, 30.26801200315832], [-97.7379254065454, 30.2677050581497]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147863}}, {"id": 6518, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73825431242585, 30.26678754746615], [-97.7379254065454, 30.2677050581497]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 106518}}, {"id": 47866, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74343013763428, 30.270261214776838], [-97.74450302124023, 30.270594791564676]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147866}}]}, "field": "project_extent_geojson"}]', '2021-03-23 16:57:49.426031+00', '40422ee4-4aca-43b3-898c-ed2ad2f88f6d', 188, 'UPDATE'); INSERT INTO public.moped_activity_log (activity_id, record_id, record_type, record_data, description, created_at, updated_by, record_project_id, operation_type) VALUES ('9ad6d931-732a-4104-830b-d6204831c725', 188, 'moped_project', '{"id": "bec8969f-a8c8-4cf0-b46d-212d28f54ec0", "event": {"op": "UPDATE", "data": {"new": {"added_by": null, "end_date": null, "date_added": "2021-03-23T16:56:35.754826+00:00", "eCapris_id": "", "is_retired": false, "project_id": 188, "start_date": "2021-03-23", "fiscal_year": "2021", "timeline_id": null, "project_name": "Another John Project", "project_uuid": "892a57ab-74d1-4033-8920-4084d6117c41", "current_phase": "complete", "project_order": null, "current_status": "active", "project_length": 0, "capitally_funded": false, "project_priority": "", "project_importance": null, "project_description": "This is gonna be good", "project_extent_geojson": {"type": "FeatureCollection", "features": [{"id": 54325, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74485321715474, 30.269660368310596], [-97.74449598044157, 30.270580023866273]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154325}}, {"id": 54442, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74521112442017, 30.268721592638997], [-97.74485321715474, 30.269660368310596]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154442}}, {"id": 54443, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74521112442017, 30.268721592638997], [-97.7462675794959, 30.269028968820365]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154443}}, {"id": 48417, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7462675794959, 30.269028968820365], [-97.74594135582447, 30.269968465464004]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 148417}}, {"id": 54312, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7441355586052, 30.26842203386127], [-97.74378100410104, 30.269352704539784]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154312}}, {"id": 47473, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74247946217656, 30.26897163447613], [-97.74378100410104, 30.269352704539784]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147473}}, {"id": 53749, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74282043799758, 30.26807093069874], [-97.7441355586052, 30.26842203386127]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 153749}}, {"id": 53748, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74282043799758, 30.26807093069874], [-97.74247946217656, 30.26897163447613]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 153748}}, {"id": 52382, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74111723527312, 30.268608516185054], [-97.74078816175461, 30.269537013352178]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 152382}}, {"id": 54322, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74146189913154, 30.267688697374666], [-97.74111723527312, 30.268608516185054]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154322}}, {"id": 47852, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74111723527312, 30.268608516185054], [-97.74004502221942, 30.268300849117793]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147852}}, {"id": 48531, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7403829805553, 30.267399994400208], [-97.74004502221942, 30.268300849117793]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 148531}}, {"id": 51717, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73934295400977, 30.267073646083887], [-97.73895654827356, 30.26801200315832]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 151717}}, {"id": 47863, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73895654827356, 30.26801200315832], [-97.7379254065454, 30.2677050581497]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147863}}, {"id": 6518, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73825431242585, 30.26678754746615], [-97.7379254065454, 30.2677050581497]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 106518}}, {"id": 47866, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74343013763428, 30.270261214776838], [-97.74450302124023, 30.270594791564676]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147866}}]}, "project_description_public": null}, "old": {"added_by": null, "end_date": null, "date_added": "2021-03-23T16:56:35.754826+00:00", "eCapris_id": "", "is_retired": false, "project_id": 188, "start_date": "2021-03-23", "fiscal_year": "2021", "timeline_id": null, "project_name": "Another John Project", "project_uuid": "892a57ab-74d1-4033-8920-4084d6117c41", "current_phase": "complete", "project_order": null, "current_status": "active", "project_length": 0, "capitally_funded": false, "project_priority": "", "project_importance": null, "project_description": "This is gonna be good", "project_extent_geojson": {"type": "FeatureCollection", "features": [{"id": 54325, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74485321715474, 30.269660368310596], [-97.74449598044157, 30.270580023866273]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154325}}, {"id": 54442, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74521112442017, 30.268721592638997], [-97.74485321715474, 30.269660368310596]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154442}}, {"id": 54443, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74521112442017, 30.268721592638997], [-97.7462675794959, 30.269028968820365]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154443}}, {"id": 48417, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7462675794959, 30.269028968820365], [-97.74594135582447, 30.269968465464004]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 148417}}, {"id": 54312, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7441355586052, 30.26842203386127], [-97.74378100410104, 30.269352704539784]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154312}}, {"id": 47473, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74247946217656, 30.26897163447613], [-97.74378100410104, 30.269352704539784]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147473}}, {"id": 53749, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74282043799758, 30.26807093069874], [-97.7441355586052, 30.26842203386127]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 153749}}, {"id": 53748, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74282043799758, 30.26807093069874], [-97.74247946217656, 30.26897163447613]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 153748}}, {"id": 52382, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74111723527312, 30.268608516185054], [-97.74078816175461, 30.269537013352178]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 152382}}, {"id": 54322, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74146189913154, 30.267688697374666], [-97.74111723527312, 30.268608516185054]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154322}}, {"id": 47852, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74111723527312, 30.268608516185054], [-97.74004502221942, 30.268300849117793]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147852}}, {"id": 48531, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7403829805553, 30.267399994400208], [-97.74004502221942, 30.268300849117793]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 148531}}, {"id": 51717, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73934295400977, 30.267073646083887], [-97.73895654827356, 30.26801200315832]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 151717}}, {"id": 47863, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73895654827356, 30.26801200315832], [-97.7379254065454, 30.2677050581497]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147863}}, {"id": 6518, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73825431242585, 30.26678754746615], [-97.7379254065454, 30.2677050581497]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 106518}}, {"id": 47866, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74343013763428, 30.270261214776838], [-97.74450302124023, 30.270594791564676]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147866}}, {"id": 48693, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74449598044157, 30.270580023866273], [-97.74560004472733, 30.27087740434517]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 148693}}]}, "project_description_public": null}}, "trace_context": {"span_id": 3679271474502845354, "trace_id": 18371608663279385474}, "session_variables": {"x-hasura-role": "moped-admin", "x-hasura-user-id": "40422ee4-4aca-43b3-898c-ed2ad2f88f6d", "x-hasura-user-db-id": "10", "x-hasura-user-wg-id": "3"}}, "table": {"name": "moped_project", "schema": "public"}, "trigger": {"name": "activity_log_moped_project"}, "created_at": "2021-03-23T16:57:42.810925Z", "delivery_info": {"max_retries": 0, "current_retry": 0}}', '[{"new": {"CTN": [154325, 154442, 154443, 148417, 154312, 147473, 153749, 153748, 152382, 154322, 147852, 148531, 151717, 147863, 106518]}, "old": {"CTN": [154325, 154442, 154443, 148417, 154312, 147473, 153749, 153748, 152382, 154322, 147852, 148531, 151717, 147863, 106518, 148693, 147866]}, "field": "project_extent_ids"}, {"new": {"type": "FeatureCollection", "features": [{"id": 54325, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74485321715474, 30.269660368310596], [-97.74449598044157, 30.270580023866273]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154325}}, {"id": 54442, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74521112442017, 30.268721592638997], [-97.74485321715474, 30.269660368310596]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154442}}, {"id": 54443, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74521112442017, 30.268721592638997], [-97.7462675794959, 30.269028968820365]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154443}}, {"id": 48417, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7462675794959, 30.269028968820365], [-97.74594135582447, 30.269968465464004]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 148417}}, {"id": 54312, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7441355586052, 30.26842203386127], [-97.74378100410104, 30.269352704539784]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154312}}, {"id": 47473, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74247946217656, 30.26897163447613], [-97.74378100410104, 30.269352704539784]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147473}}, {"id": 53749, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74282043799758, 30.26807093069874], [-97.7441355586052, 30.26842203386127]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 153749}}, {"id": 53748, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74282043799758, 30.26807093069874], [-97.74247946217656, 30.26897163447613]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 153748}}, {"id": 52382, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74111723527312, 30.268608516185054], [-97.74078816175461, 30.269537013352178]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 152382}}, {"id": 54322, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74146189913154, 30.267688697374666], [-97.74111723527312, 30.268608516185054]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154322}}, {"id": 47852, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74111723527312, 30.268608516185054], [-97.74004502221942, 30.268300849117793]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147852}}, {"id": 48531, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7403829805553, 30.267399994400208], [-97.74004502221942, 30.268300849117793]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 148531}}, {"id": 51717, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73934295400977, 30.267073646083887], [-97.73895654827356, 30.26801200315832]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 151717}}, {"id": 47863, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73895654827356, 30.26801200315832], [-97.7379254065454, 30.2677050581497]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147863}}, {"id": 6518, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73825431242585, 30.26678754746615], [-97.7379254065454, 30.2677050581497]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 106518}}, {"id": 47866, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74343013763428, 30.270261214776838], [-97.74450302124023, 30.270594791564676]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147866}}]}, "old": {"type": "FeatureCollection", "features": [{"id": 54325, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74485321715474, 30.269660368310596], [-97.74449598044157, 30.270580023866273]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154325}}, {"id": 54442, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74521112442017, 30.268721592638997], [-97.74485321715474, 30.269660368310596]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154442}}, {"id": 54443, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74521112442017, 30.268721592638997], [-97.7462675794959, 30.269028968820365]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154443}}, {"id": 48417, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7462675794959, 30.269028968820365], [-97.74594135582447, 30.269968465464004]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 148417}}, {"id": 54312, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7441355586052, 30.26842203386127], [-97.74378100410104, 30.269352704539784]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154312}}, {"id": 47473, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74247946217656, 30.26897163447613], [-97.74378100410104, 30.269352704539784]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147473}}, {"id": 53749, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74282043799758, 30.26807093069874], [-97.7441355586052, 30.26842203386127]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 153749}}, {"id": 53748, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74282043799758, 30.26807093069874], [-97.74247946217656, 30.26897163447613]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 153748}}, {"id": 52382, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74111723527312, 30.268608516185054], [-97.74078816175461, 30.269537013352178]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 152382}}, {"id": 54322, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74146189913154, 30.267688697374666], [-97.74111723527312, 30.268608516185054]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 154322}}, {"id": 47852, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74111723527312, 30.268608516185054], [-97.74004502221942, 30.268300849117793]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147852}}, {"id": 48531, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.7403829805553, 30.267399994400208], [-97.74004502221942, 30.268300849117793]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 148531}}, {"id": 51717, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73934295400977, 30.267073646083887], [-97.73895654827356, 30.26801200315832]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 151717}}, {"id": 47863, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73895654827356, 30.26801200315832], [-97.7379254065454, 30.2677050581497]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147863}}, {"id": 6518, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.73825431242585, 30.26678754746615], [-97.7379254065454, 30.2677050581497]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 106518}}, {"id": 47866, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74343013763428, 30.270261214776838], [-97.74450302124023, 30.270594791564676]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 147866}}, {"id": 48693, "type": "Feature", "geometry": {"type": "LineString", "coordinates": [[-97.74449598044157, 30.270580023866273], [-97.74560004472733, 30.27087740434517]]}, "properties": {"sourceLayer": "CTN", "PROJECT_EXTENT_ID": 148693}}]}, "field": "project_extent_geojson"}]', '2021-03-23 16:57:54.283067+00', '40422ee4-4aca-43b3-898c-ed2ad2f88f6d', 188, 'UPDATE'); @@ -1424,4 +1424,3 @@ SELECT pg_catalog.setval('public.moped_workgroup_workgroup_id_seq', 1, true); -- -- PostgreSQL database dump complete -- - diff --git a/moped-editor/src/queries/project.js b/moped-editor/src/queries/project.js index 616c0b77fc..7e038c4485 100644 --- a/moped-editor/src/queries/project.js +++ b/moped-editor/src/queries/project.js @@ -1,12 +1,8 @@ import { gql } from "@apollo/client"; export const ADD_PROJECT = gql` - mutation AddProject( - $object: moped_project_insert_input! - ) { - insert_moped_project_one( - object: $object - ) { + mutation AddProject($object: moped_project_insert_input!) { + insert_moped_project_one(object: $object) { project_id project_name project_description @@ -17,6 +13,13 @@ export const ADD_PROJECT = gql` fiscal_year capitally_funded start_date + moped_proj_components { + moped_proj_features_components { + moped_proj_feature { + feature_id + } + } + } } } `; @@ -34,7 +37,7 @@ export const SUMMARY_QUERY = gql` ecapris_subproject_id fiscal_year project_priority - moped_proj_features(where: {status_id: {_eq: 1}}) { + moped_proj_features(where: { status_id: { _eq: 1 } }) { feature_id project_id location @@ -84,7 +87,10 @@ export const TEAM_QUERY = gql` workgroup_id workgroup_name } - moped_project_roles(order_by: {role_order: asc}, where: {project_role_id: {_gt: 0}}) { + moped_project_roles( + order_by: { role_order: asc } + where: { project_role_id: { _gt: 0 } } + ) { project_role_id project_role_name project_role_description @@ -106,16 +112,10 @@ export const UPSERT_PROJECT_PERSONNEL = gql` $objects: [moped_proj_personnel_insert_input!]! ) { insert_moped_proj_personnel( - objects: $objects, + objects: $objects on_conflict: { - constraint: moped_proj_personnel_project_id_user_id_role_id_key, - update_columns: [ - project_id, - user_id, - role_id - status_id, - notes, - ] + constraint: moped_proj_personnel_project_id_user_id_role_id_key + update_columns: [project_id, user_id, role_id, status_id, notes] } ) { affected_rows @@ -156,17 +156,20 @@ export const UPDATE_PROJECT_PERSONNEL = gql` export const TIMELINE_QUERY = gql` query TeamTimeline($projectId: Int) { - moped_phases(where: { phase_id: {_gt: 0} }) { + moped_phases(where: { phase_id: { _gt: 0 } }) { phase_id phase_name subphases } - moped_subphases(where: { subphase_id: {_gt: 0} }, order_by: { subphase_order: asc }) { + moped_subphases( + where: { subphase_id: { _gt: 0 } } + order_by: { subphase_order: asc } + ) { subphase_id subphase_name } moped_proj_phases( - where: { project_id: { _eq: $projectId }, status_id: {_eq: 1} } + where: { project_id: { _eq: $projectId }, status_id: { _eq: 1 } } order_by: { phase_start: desc } ) { phase_name @@ -178,12 +181,12 @@ export const TIMELINE_QUERY = gql` subphase_name subphase_id } - moped_milestones(where: { milestone_id: {_gt: 0} }) { + moped_milestones(where: { milestone_id: { _gt: 0 } }) { milestone_id milestone_name } moped_proj_milestones( - where: { project_id: { _eq: $projectId }, status_id: {_eq: 1} } + where: { project_id: { _eq: $projectId }, status_id: { _eq: 1 } } order_by: { milestone_end: desc } ) { milestone_name @@ -204,8 +207,8 @@ export const UPDATE_PROJECT_PHASES_MUTATION = gql` $phase_end: date = null $project_phase_id: Int! $phase_name: String! - $subphase_id: Int = 0, - $subphase_name: String = null, + $subphase_id: Int = 0 + $subphase_name: String = null ) { update_moped_proj_phases_by_pk( pk_columns: { project_phase_id: $project_phase_id } @@ -261,19 +264,25 @@ export const UPDATE_PROJECT_MILESTONES_MUTATION = gql` `; export const DELETE_PROJECT_PHASE = gql` - mutation DeleteProjectPhase($project_phase_id: Int!) { - update_moped_proj_phases(_set: {status_id: 0}, where: {project_phase_id: {_eq: $project_phase_id}}) { - affected_rows - } + mutation DeleteProjectPhase($project_phase_id: Int!) { + update_moped_proj_phases( + _set: { status_id: 0 } + where: { project_phase_id: { _eq: $project_phase_id } } + ) { + affected_rows } + } `; export const DELETE_PROJECT_MILESTONE = gql` - mutation DeleteProjectMilestone($project_milestone_id: Int!) { - update_moped_proj_milestones(_set: {status_id: 0}, where: {project_milestone_id: {_eq: $project_milestone_id}}) { - affected_rows - } + mutation DeleteProjectMilestone($project_milestone_id: Int!) { + update_moped_proj_milestones( + _set: { status_id: 0 } + where: { project_milestone_id: { _eq: $project_milestone_id } } + ) { + affected_rows } + } `; export const ADD_PROJECT_PHASE = gql` @@ -295,7 +304,9 @@ export const ADD_PROJECT_PHASE = gql` `; export const ADD_PROJECT_MILESTONE = gql` - mutation AddProjectMilestone($objects: [moped_proj_milestones_insert_input!]!) { + mutation AddProjectMilestone( + $objects: [moped_proj_milestones_insert_input!]! + ) { insert_moped_proj_milestones(objects: $objects) { returning { milestone_name @@ -312,9 +323,7 @@ export const ADD_PROJECT_MILESTONE = gql` `; export const UPSERT_PROJECT_EXTENT = gql` - mutation UpsertProjectExtent( - $upserts: [moped_proj_features_insert_input!]! - ) { + mutation UpsertProjectExtent($upserts: [moped_proj_features_insert_input!]!) { insert_moped_proj_features( objects: $upserts on_conflict: { @@ -346,9 +355,7 @@ export const PROJECT_ACTIVITY_LOG = gql` user_id } } - moped_users(where:{ - status_id:{_eq: 1} - }) { + moped_users(where: { status_id: { _eq: 1 } }) { first_name last_name user_id @@ -391,10 +398,7 @@ export const PROJECT_ACTIVITY_LOG_DETAILS = gql` export const PROJECT_FILE_ATTACHMENTS = gql` query MopedProjectFiles($projectId: Int!) { moped_project_files( - where: { - project_id: {_eq: $projectId}, - is_retired: {_eq: false} - } + where: { project_id: { _eq: $projectId }, is_retired: { _eq: false } } ) { project_file_id project_id @@ -417,20 +421,13 @@ export const PROJECT_FILE_ATTACHMENTS = gql` export const PROJECT_FILE_ATTACHMENTS_UPDATE = gql` mutation UpdateProjectFileAttachment( - $fileId: Int!, - $fileName: String!, + $fileId: Int! + $fileName: String! $fileDescription: String! ) { update_moped_project_files( - where: { - project_file_id: { - _eq: $fileId - } - }, - _set: { - file_name: $fileName, - file_description: $fileDescription - } + where: { project_file_id: { _eq: $fileId } } + _set: { file_name: $fileName, file_description: $fileDescription } ) { affected_rows } @@ -438,18 +435,10 @@ export const PROJECT_FILE_ATTACHMENTS_UPDATE = gql` `; export const PROJECT_FILE_ATTACHMENTS_DELETE = gql` - mutation DeleteProjectFileAttachment( - $fileId: Int!, - ) { + mutation DeleteProjectFileAttachment($fileId: Int!) { update_moped_project_files( - where: { - project_file_id: { - _eq: $fileId - } - }, - _set: { - is_retired: true, - } + where: { project_file_id: { _eq: $fileId } } + _set: { is_retired: true } ) { affected_rows } @@ -457,23 +446,25 @@ export const PROJECT_FILE_ATTACHMENTS_DELETE = gql` `; export const PROJECT_FILE_ATTACHMENTS_CREATE = gql` - mutation insert_single_article($object: moped_project_files_insert_input! ) { + mutation insert_single_article($object: moped_project_files_insert_input!) { insert_moped_project_files(objects: [$object]) { affected_rows } } `; -export const PROJECT_ARCHIVE= gql` +export const PROJECT_ARCHIVE = gql` mutation ArchiveMopedProject($projectId: Int!) { - update_moped_project(where: {project_id: {_eq: $projectId}}, _set: {is_retired: true}) { + update_moped_project( + where: { project_id: { _eq: $projectId } } + _set: { is_retired: true } + ) { affected_rows } } -` +`; -export const PROJECT_CLEAR_MAP_DATA_TEMPLATE = - `mutation ClearProjectMapData($projectId: Int!) { +export const PROJECT_CLEAR_MAP_DATA_TEMPLATE = `mutation ClearProjectMapData($projectId: Int!) { update_moped_proj_features( where: { project_id: { _eq: $projectId } } _set: { @@ -505,3 +496,154 @@ export const PROJECT_CLEAR_MAP_DATA_TEMPLATE = } } `; + +export const COMPONENTS_QUERY = gql` + query GetComponents($projectId: Int) { + moped_proj_components( + where: { project_id: { _eq: $projectId }, status_id: { _eq: 1 } } + ) { + project_component_id + project_id + component_id + name + description + moped_components { + component_type: component_name + component_subtype + } + moped_proj_components_subcomponents(where: { status_id: { _eq: 1 } }) { + status_id + moped_subcomponent { + subcomponent_id + subcomponent_name + } + } + moped_proj_features_components(where: { status_id: { _eq: 1 } }) { + name + moped_proj_component_id + status_id + moped_proj_feature { + location + feature_id + status_id + } + } + } + } +`; + +export const COMPONENT_DETAILS_QUERY = gql` + query GetComponentDetails($componentId: Int!) { + moped_proj_components( + where: { project_component_id: { _eq: $componentId } } + ) { + component_id + description + name + project_component_id + project_id + status_id + moped_components { + component_name + component_id + component_subtype + } + moped_proj_features_components(where: { status_id: { _eq: 1 } }) { + project_features_components_id + moped_proj_features_id + status_id + moped_proj_feature { + location + feature_id + status_id + project_id + } + } + moped_proj_components_subcomponents(where: { status_id: { _eq: 1 } }) { + component_subcomponent_id + project_component_id + subcomponent_id + status_id + moped_subcomponent { + subcomponent_id + subcomponent_name + } + } + } + moped_subcomponents { + subcomponent_name + subcomponent_id + component_id + } + moped_components { + component_name + component_subtype + component_id + line_representation + } + } +`; + +export const UPDATE_MOPED_COMPONENT = gql` + mutation UpdateMopedComponent( + $objects: [moped_proj_components_insert_input!]! + ) { + insert_moped_proj_components( + objects: $objects + on_conflict: { + constraint: moped_proj_components_pkey + update_columns: [component_id, description, status_id] + } + ) { + affected_rows + } + } +`; + +export const DELETE_MOPED_COMPONENT = gql` + mutation DeleteMopedComponent($projComponentId: Int!) { + update_moped_proj_components( + where: { project_component_id: { _eq: $projComponentId } } + _set: { status_id: 0 } + ) { + affected_rows + } + update_moped_proj_components_subcomponents( + where: { project_component_id: { _eq: $projComponentId } } + _set: { status_id: 0 } + ) { + affected_rows + } + update_moped_proj_features_components( + where: { + moped_proj_component: { + project_component_id: { _eq: $projComponentId } + } + } + _set: { status_id: 0 } + ) { + affected_rows + } + update_moped_proj_features( + where: { + moped_proj_features_components: { + moped_proj_component_id: { _eq: $projComponentId } + } + } + _set: { status_id: 0 } + ) { + affected_rows + } + } +`; + +export const UPDATE_NEW_PROJ_FEATURES = gql` + mutation UpdateNewProjectFeatures($featureList: [Int!]!, $projectId: Int!) { + update_moped_proj_features( + where: { feature_id: { _in: $featureList } } + _set: { project_id: $projectId } + ) { + affected_rows + } + } +`; diff --git a/moped-editor/src/utils/mapHelpers.js b/moped-editor/src/utils/mapHelpers.js index ee978a2283..8f5d7036bd 100644 --- a/moped-editor/src/utils/mapHelpers.js +++ b/moped-editor/src/utils/mapHelpers.js @@ -345,9 +345,23 @@ export const getLayerSource = e => */ export const createFeatureCollectionFromProjectFeatures = projectFeatureRecords => ({ type: "FeatureCollection", - features: projectFeatureRecords - ? projectFeatureRecords.map(feature => feature.location) - : [], + features: projectFeatureRecords // Do we have a records object? + ? // We do, then unpack features + projectFeatureRecords.reduce( + (accumulator, feature) => [ + // First copy the current state of the accumulator + ...accumulator, + // Then we must copy any individual feature (or features) + ...(feature.location.type === "FeatureCollection" + ? // We must unpack the features + feature.location.features + : // Provide the regular feature + [feature.location]), + ], + [] // Initial accumulator state + ) + : // We don't, return empty array + [], }); /** diff --git a/moped-editor/src/views/projects/newProjectView/NewProjectMap.js b/moped-editor/src/views/projects/newProjectView/NewProjectMap.js index c9f8346d23..436d29c25b 100644 --- a/moped-editor/src/views/projects/newProjectView/NewProjectMap.js +++ b/moped-editor/src/views/projects/newProjectView/NewProjectMap.js @@ -43,6 +43,10 @@ export const useStyles = makeStyles({ padding: 25, position: "relative", }, + mapBoxNoPadding: { + padding: 0, + position: "relative", + }, geocoderContainer: { display: "flex", height: 50, @@ -62,6 +66,7 @@ const NewProjectMap = ({ setFeatureCollection, projectId = null, refetchProjectDetails = null, + noPadding = false, }) => { const classes = useStyles(); const mapRef = useRef(); @@ -178,7 +183,7 @@ const NewProjectMap = ({ }; return ( - + {/* Render these controls outside ReactMapGL so mouse events don't propagate to the map */}
{renderLayerSelect()} diff --git a/moped-editor/src/views/projects/newProjectView/NewProjectView.js b/moped-editor/src/views/projects/newProjectView/NewProjectView.js index 190309a9a0..8d4a771597 100644 --- a/moped-editor/src/views/projects/newProjectView/NewProjectView.js +++ b/moped-editor/src/views/projects/newProjectView/NewProjectView.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { useNavigate } from "react-router-dom"; import { Button, @@ -20,7 +20,10 @@ import NewProjectTeam from "./NewProjectTeam"; import NewProjectMap from "./NewProjectMap"; import Page from "src/components/Page"; import { useMutation } from "@apollo/client"; -import { ADD_PROJECT } from "../../../queries/project"; +import { + ADD_PROJECT, + UPDATE_NEW_PROJ_FEATURES, +} from "../../../queries/project"; import { filterObjectByKeys } from "../../../utils/materialTableHelpers"; import { countFeatures, mapErrors, mapConfig } from "../../../utils/mapHelpers"; @@ -76,6 +79,16 @@ const NewProjectView = () => { } }, [success, newProjectId, navigate]); + /** + * Form State + * @type {Number} activeStep - The current step being rendered + * @type {Object} projectDetails - The current state of project details + * @type {boolean} nameError - When true, it denotes an error in the name + * @type {boolean} descriptionError - When true, it denotes an error in the project description + * @type {Object[]} personnel - An array of objects containing the personnel data + * @type {Object} featureCollection - The final GeoJSON to be inserted into a component + * @type {boolean} areNoFeaturesSelected - True when no features are selected + */ const [activeStep, setActiveStep] = useState(0); const [projectDetails, setProjectDetails] = useState({ fiscal_year: "", @@ -89,13 +102,11 @@ const NewProjectView = () => { }); const [nameError, setNameError] = useState(false); const [descriptionError, setDescriptionError] = useState(false); - const [personnel, setPersonnel] = useState([]); const [featureCollection, setFeatureCollection] = useState({ type: "FeatureCollection", features: [], }); - const [areNoFeaturesSelected, setAreNoFeaturesSelected] = useState(false); // Reset areNoFeaturesSelected once a feature is selected to remove error message @@ -107,6 +118,10 @@ const NewProjectView = () => { } }, [featureCollection]); + /** + * Generates an object with labels for the steps + * @return {Object[]} + */ const getSteps = () => { return [ { label: "Define project" }, @@ -119,6 +134,11 @@ const NewProjectView = () => { ]; }; + /** + * Returns a component for a specific step number + * @param {Number} step - The step number component to render + * @return {JSX.Element|string} + */ const getStepContent = step => { switch (step) { case 0: @@ -145,8 +165,18 @@ const NewProjectView = () => { return "Unknown step"; } }; + + /** + * A constant object with all the steps generated by getSteps + * @constant + * @type {Object} + */ const steps = getSteps(); + /** + * Handles the logic for the "Next" button + * @return {string} + */ const handleNext = () => { let nameError = projectDetails.project_name.length === 0; let descriptionError = projectDetails.project_description.length === 0; @@ -175,6 +205,10 @@ const NewProjectView = () => { setDescriptionError(descriptionError); }; + /** + * Handles the back step button logic + * @return {string} + */ const handleBack = () => { if (activeStep > 0) { setActiveStep(prevActiveStep => prevActiveStep - 1); @@ -189,15 +223,26 @@ const NewProjectView = () => { } }; + /** + * Handles the reset button logic + */ const handleReset = () => { setActiveStep(0); }; + /** + * Add Project Apollo Mutation + */ const [addProject] = useMutation(ADD_PROJECT); + const [updateFeatures] = useMutation(UPDATE_NEW_PROJ_FEATURES); - const timer = React.useRef(); + /** + * Timer Reference Object + * @type {React.MutableRefObject} + */ + const timer = useRef(); - React.useEffect(() => { + useEffect(() => { const currentTimer = timer.current; return () => { @@ -205,6 +250,9 @@ const NewProjectView = () => { }; }, []); + /** + * Persists a new project into the database + */ const handleSubmit = () => { if (countFeatures(featureCollection) < mapConfig.minimumFeaturesInProject) { setAreNoFeaturesSelected(true); @@ -238,24 +286,76 @@ const NewProjectView = () => { ...filterObjectByKeys(row, ["tableData"]), })); - const projectFeatures = featureCollection.features.map(feature => ({ - location: feature, - status_id: 1, - })); - - addProject({ - variables: { - object: { - ...projectDetails, - moped_proj_features: { data: projectFeatures }, - moped_proj_personnel: { data: cleanedPersonnel }, + /** + * We now must generate the payload with variables for our GraphQL query. + * @type {Object} + */ + const variablePayload = { + object: { + // First we need to copy the project details + ...projectDetails, + // Next we generate the project extent component + moped_proj_components: { + data: [ + { + name: "Extent", + description: "Project full extent", + component_id: 0, + status_id: 1, + moped_proj_features_components: { + data: featureCollection.features.map(feature => ({ + name: "Feature Extent Component", + description: "New Project Feature Extent", + status_id: 1, + moped_proj_feature_object: { + data: { + status_id: 1, + location: feature, + }, + }, + })), + }, + }, + ], }, + // Finally we provide the project personnel + moped_proj_personnel: { data: cleanedPersonnel }, }, + }; + + /** + * Persist the new project to database + */ + addProject({ + variables: variablePayload, }) + // On success .then(response => { - const { project_id } = response.data.insert_moped_project_one; - setNewProjectId(project_id); + // Destructure the data we need from the response + const { + project_id, + moped_proj_components, + } = response.data.insert_moped_project_one; + + // Retrieve the feature_ids that need to be updated + const featuresToUpdate = moped_proj_components[0].moped_proj_features_components.map( + featureComponent => featureComponent.moped_proj_feature.feature_id + ); + + // Persist the feature updates, we must. + updateFeatures({ + variables: { + featureList: featuresToUpdate, + projectId: project_id, + }, + }) + .then(() => setNewProjectId(project_id)) + .catch(err => { + alert(err); + setNewProjectId(project_id); + }); }) + // If there is an error, we must show it... .catch(err => { alert(err); setLoading(false); diff --git a/moped-editor/src/views/projects/projectView/ProjectComponentEdit.js b/moped-editor/src/views/projects/projectView/ProjectComponentEdit.js new file mode 100644 index 0000000000..5ae6481d6b --- /dev/null +++ b/moped-editor/src/views/projects/projectView/ProjectComponentEdit.js @@ -0,0 +1,822 @@ +import React, { useEffect, useState } from "react"; +import { useMutation, useQuery } from "@apollo/client"; +import { + Button, + CircularProgress, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + FormControl, + Grid, + Icon, + TextField, +} from "@material-ui/core"; +import makeStyles from "@material-ui/core/styles/makeStyles"; +import { + COMPONENT_DETAILS_QUERY, + UPDATE_MOPED_COMPONENT, + DELETE_MOPED_COMPONENT, +} from "../../../queries/project"; +import ProjectComponentSubcomponents from "./ProjectComponentSubcomponents"; + +import NewProjectMap from "../newProjectView/NewProjectMap"; +import { Alert, Autocomplete } from "@material-ui/lab"; +import { countFeatures, mapConfig, mapErrors } from "../../../utils/mapHelpers"; +import { filterObjectByKeys } from "../../../utils/materialTableHelpers"; +import { useParams } from "react-router-dom"; + +const useStyles = makeStyles(theme => ({ + title: { + marginLeft: theme.spacing(2), + flex: 1, + }, + selectEmpty: { + marginTop: theme.spacing(2), + }, + formSelect: { + width: "60%", + }, + formButton: { + margin: theme.spacing(2), + }, + formButtonDelete: { + float: "right", + margin: theme.spacing(2), + }, + formTextField: { + margin: theme.spacing(2), + }, + mapAlert: { + margin: theme.spacing(2), + }, +})); + +/** + * The project component editor + * @param {Number} componentId - The moped_proj_component id being edited + * @param {function} handleCancelEdit - The function to call if we need to cancel editing + * @param {function} projectRefetchFeatures - Reload parent component's features + * @return {JSX.Element} + * @constructor + */ +const ProjectComponentEdit = ({ + componentId, + handleCancelEdit, + projectRefetchFeatures, +}) => { + const { projectId } = useParams(); + const classes = useStyles(); + const emptyFeatureCollection = { + type: "FeatureCollection", + features: [], + }; + + /** + * The State + * @type {String} selectedComponentType - A string containing the name of the selected type in lowercase + * @type {String} selectedComponentSubtype - A string containing the name of the selected subtype in lowercase + * @type {String[]} selectedComponentSubtype - A string list containing all available subtypes for type + * @type {Number[]} selectedSubcomponents - A number array containing the id of the selected subcomponents + * @type {String[]} availableSubtypes - An string list containing all available subtypes for the selected component + * @type {Object[]} editFeatureComponents - An object list containing the features for this component + * @type {Object} editFeatureCollection - The final GeoJson generated for all the the features in this component + * @type {String} componentDescription - The description of this component + * @type {boolean} deleteDialogOpen - If true, it displays the delete dialog, or hides it if false. + * @constant + */ + const [selectedComponentId, setSelectedComponentId] = useState(null); + const [selectedComponentType, setSelectedComponentType] = useState(null); + const [selectedComponentSubtype, setSelectedComponentSubtype] = useState( + null + ); + const [selectedSubcomponents, setSelectedSubcomponents] = useState([]); + const [availableSubtypes, setAvailableSubtypes] = useState([]); + const [editFeatureComponents, setEditFeatureComponents] = useState([]); + const [editFeatureCollection, setEditFeatureCollection] = useState( + emptyFeatureCollection + ); + + const [componentDescription, setComponentDescription] = useState(null); + + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + + /** + * Apollo hook functions + */ + const { loading, data, error } = useQuery(COMPONENT_DETAILS_QUERY, { + variables: { + componentId: componentId, + }, + fetchPolicy: "no-cache", + }); + + const [updateProjectComponents] = useMutation(UPDATE_MOPED_COMPONENT); + + const [deleteProjectComponent] = useMutation(DELETE_MOPED_COMPONENT); + + /** + * Generates an initial list of component types, subtypes and counts + */ + const initialTypeCounts = data // Do we have data? + ? // Yes, let's get the counts by using reduce + data.moped_components.reduce((accumulator, component, index) => { + // Retrieve the current component's values, in lower case + const componentId = component?.component_id ?? null; + const componentName = (component?.component_name ?? "").toLowerCase(); + const componentSubtype = ( + component?.component_subtype ?? "" + ).toLowerCase(); + + // Then, retrieve the subcomponents associated to this component_id by filtering + const componentSubcomponents = data.moped_subcomponents.filter( + subcomponent => subcomponent.component_id === componentId + ); + + // Get the total count for this component name + const currentCount = accumulator[componentName]?.count ?? 0; + + // Send back to the accumulator a copy of itself plus new data: + return { + // Here is the copy of the current state of the output: + ...accumulator, + // And for new data, create (or overwrite) a new key with the component name + [componentName]: { + count: currentCount + 1, // Assign count to currentCount + 1 + /** + * If currentCount is zero, it means this is the first iteration of reduce + * for this componentName, which means this should be considered a single item + * and we need to give it its component_id. + * + * Otherwise, this is another iteration (n+1) for this componentName, which means + * componentName is a group. Let's give it an id of zero as a lazy way to categorize + * and differentiate groups from single items. + */ + component_id: currentCount > 1 ? 0 : componentId, + // Provide Index for context + index: index, + // Initialize a subtypes key with an object containing componentSubtype information. + subtypes: { + // Copy the current state of the accumulator's subtypes + ...(accumulator[componentName]?.subtypes ?? {}), + /** + * Create (or overwrite) a new subtype by the name of componentSubtype, + * containing all the data of this component iteration. + */ + [componentSubtype]: { + component_id: componentId, + component_name: component?.component_name ?? null, + component_subtype: component?.component_subtype ?? null, + }, + }, + subcomponents: componentSubcomponents, + }, + }; + }, {}) + : {}; + + // Now check if we have any subcomponents in the DB + const subcomponentsDB = + /** + * Get moped_proj_components or assume empty array, if the array is empty it means + * the user has not selected a component; which should never be the case. Do we have + * a selected component? (is the count greater than zero?) + */ + (data?.moped_proj_components ?? []).length > 0 + ? // Yes, we have project a component, now gather a list of subcomponents for that component + data.moped_proj_components[0].moped_proj_components_subcomponents.map( + subcomponent => ({ + ...subcomponent.moped_subcomponent, + component_subcomponent_id: subcomponent.component_subcomponent_id, + }) + ) + : // Nothing to do here, no valid component is selected. + []; + + /** + * This is a unique sorted list containing the names of available components + * @type {String[]} + */ + const availableTypes = data + ? [ + ...new Set( + data.moped_components.map( + moped_component => moped_component.component_name + ) + ), + ].sort() + : []; + + /** + * Generates a list of available subtypes for a fiven type name + * @param {String} type - The type name + * @return {String[]} - A string array with the available subtypes + */ + const getAvailableSubtypes = type => + // For every subtype found in initialTypeCounts[type] + Object.entries(initialTypeCounts[type]?.subtypes ?? {}) + // Destructure the key and value (_ & component, respectively) and gather the name + .map(([_, component]) => (component?.component_subtype ?? "").trim()) + .filter(item => item.length > 0); // Keep only if the name is not empty + + /** + * Returns any available subcomponent objects for a specific type + * @param {String} type - Lower case of the name of the type + * @return {Object[]} + */ + const getAvailableSubcomponents = () => + initialTypeCounts[selectedComponentType].subcomponents; + + /** + * Returns the current project componetn id + * @return {number|null} + */ + const getProjectComponentId = () => + data?.moped_proj_components[0]?.project_component_id ?? null; + + /** + * Handles the delete button click + */ + const handleDeleteDialogClickOpen = () => { + setDeleteDialogOpen(true); + }; + + /** + * Handles the closing of the dialog + */ + const handleDeleteDialogClickClose = () => { + setDeleteDialogOpen(false); + }; + + /** + * On select, it changes the available items for that specific type + * @param {Object} e - The Object Type + * @param {String} newValue - The new value from the autocomplete selector + */ + const handleComponentTypeSelect = (e, newValue) => { + const selectedType = (newValue ?? e.target.value ?? "").toLowerCase(); + + // Generates a list of available component subtypes given a component type + const availableSubTypes = getAvailableSubtypes(selectedType); + + // Set The selected component type + setSelectedComponentType(selectedType); + setAvailableSubtypes(availableSubTypes); + setSelectedComponentSubtype(null); + }; + + /** + * On select, it changes the value of selectedSubtype state + * @param {Object} e - The event object + * @param {String} newValue - The new value from the autocomplete selector + */ + const handleComponentSubtypeSelect = (e, newValue) => + setSelectedComponentSubtype( + (newValue ?? e.target.value ?? "").toLowerCase() + ); + + /** + * Retrieves the component_id based on the type and subtype names + * @param {string} type - The type name + * @param {string} subtype - The subtype name + */ + const getSelectedComponentId = (type, subtype) => + initialTypeCounts[type].count > 1 // Is it a group? + ? // Yes, we need to look for the subtype's component_id + initialTypeCounts[type].subtypes[subtype ?? ""]?.component_id ?? null + : // No, then it's safe to get the component_id from the parent + initialTypeCounts[type]?.component_id ?? null; + + /** + * Returns true if the given type needs a subtype + * @param {string} type + * @return {boolean} + */ + const isSubtypeOptional = type => + (initialTypeCounts[type]?.count ?? 0) === 1 || + Object.keys(initialTypeCounts[type]?.subtypes ?? {}).includes(""); + + /** + * Calls upsert project features mutation, refetches data, and handles dialog close on success + */ + const generateMapUpserts = () => { + const editedFeatures = editFeatureCollection.features; + // Find new records that need to be inserted and create a feature record from them + const newFeaturesToInsert = editedFeatures + .filter( + newFeature => + !editFeatureComponents.find( + existingRecord => + newFeature?.properties?.PROJECT_EXTENT_ID === + existingRecord.properties.PROJECT_EXTENT_ID + ) + ) + .map(newFeature => ({ + ...newFeature, + status_id: 1, + })); + + // Find existing records that need to be soft deleted, clean them, and set status to inactive + const existingFeaturesToDelete = + componentId !== 0 + ? editFeatureComponents + .map(record => filterObjectByKeys(record, ["__typename"])) + .filter( + record => + !editedFeatures.find( + feature => + feature.properties.PROJECT_EXTENT_ID === + record.properties.PROJECT_EXTENT_ID + ) + ) + .map(record => ({ + ...record, + feature_id: record.properties.moped_proj_feature_id, + status_id: 0, + })) + : []; // if this is a new component, there are no old records to update + + return [...newFeaturesToInsert, ...existingFeaturesToDelete]; + }; + + /** + * Generates a list of subcomponents to be upserted in Hasura + * @return {Object[]} + */ + const generateSubcomponentUpserts = () => { + // If there are no subcomponents, then remove them all if present. + const removeAllSubcomponents = getAvailableSubcomponents().length === 0; + + // Generate a list of subcomponents to be removed + const removalList = subcomponentsDB.filter( + // Check every old subcomponent + oldSubcomponent => + // If not found, mark it as true so it's part of the selectedSubcomponents list + removeAllSubcomponents || + !selectedSubcomponents.find( + newSubcomponent => + // Return true if we have found the old subcomponent in this list + oldSubcomponent.subcomponent_id === newSubcomponent.subcomponent_id + ) + ); + + // Remove existing subcomponents, keep only those that need inserting + const insertionList = selectedSubcomponents.filter( + // If the subcomponent does not have component_subcomponent_id, then keep + // Otherwise, it means it already exists in the database + subcomponent => isNaN(subcomponent?.component_subcomponent_id) + ); + + // Generate output, clean up & return + return ( + [...insertionList, ...removalList] // Mix both insertion and removal list + // For each subcomponent, mark for deletion (status_id = 0) if component_subcomponent_id has a number + .map(subcomponent => ({ + ...subcomponent, + status_id: isNaN(subcomponent?.component_subcomponent_id) ? 1 : 0, + })) + // Then remove certain objects by their key names from the output + .map(record => + filterObjectByKeys(record, [ + "__typename", + "component_id", + "subcomponent_name", + ]) + ) + ); + }; + + /** + * Handles the key down events for the description field + * @param {Object} e - The event object + */ + const handleDescriptionKeyDown = e => { + setComponentDescription(e.target.value); + }; + + /** + * Handles the exit of the current edit screen and reloads + */ + const exitAndReload = () => { + handleCancelEdit(); + projectRefetchFeatures(); + }; + + /** + * Persists the changes to the database + */ + const handleSaveButtonClick = () => { + // Retrieve current project component id + const projComponentId = getProjectComponentId(); + + // First, we need a list of map changes only + const mapListOfChanges = generateMapUpserts(); + + // For each change, we generate a list of feature component objects + const featureComponents = mapListOfChanges.map(feature => ({ + // Each feature component shares the status of it's child feature + status_id: feature.status_id, + // Now we must determine if the feature has a nested `project_features_components_id` field + ...(feature?.properties?.project_features_components_id ?? null + ? // If so, then add it to the object, so that it can be upserted + { + project_features_components_id: + feature.properties.project_features_components_id, + } + : // If not, ignore it so that we can insert a new one + {}), + // Create the moped_proj_feature_object key with conflict rules and data + moped_proj_feature_object: { + on_conflict: { + constraint: "moped_proj_features_pkey", + update_columns: ["location", "status_id"], + }, + // This inserts into moped_proj_features, and assumes relationship with moped_proj_features_components + data: { + ...(feature.hasOwnProperty("feature_id") + ? { feature_id: feature.feature_id } + : {}), + project_id: Number.parseInt(projectId), + location: { ...feature }, + status_id: feature.status_id, + }, + }, + })); + + // 2. Generate a list of subcomponent upserts + const subcomponentChanges = generateSubcomponentUpserts(); + + /** + * This document represents all the variables Hasura needs + * to create a moped_proj_component in Hasura. It uses Hasura's + * nested insertion to establish relationships when creating + * new objects, and it uses primary keys when upserting. + * @type {moped_proj_component} + */ + const variablePayload = { + /* + First we need a component's common fields: name, status, etc. + */ + name: "", + project_id: Number.parseInt(projectId), + status_id: 1, + component_id: selectedComponentId, + description: componentDescription, + /* + To update a component, we need it's primary key. + If the project component is new, it will not have a projComponentId, then it's safe to ignore. + */ + ...(!!projComponentId ? { project_component_id: projComponentId } : {}), + + /* + Now we need to insert the subcomponents previously generated. + */ + moped_proj_components_subcomponents: { + data: subcomponentChanges, + on_conflict: { + constraint: "moped_proj_components_subcomponents_pkey", + update_columns: [ + "project_component_id", + "subcomponent_id", + "status_id", + ], + }, + }, + /* + Finally we inject the features components & geojson features + as previously generated. + */ + moped_proj_features_components: { + data: featureComponents, + on_conflict: { + constraint: "moped_proj_features_components_pkey", + update_columns: [ + "moped_proj_features_id", + "moped_proj_component_id", + "name", + "description", + "status_id", + ], + }, + }, + }; + + // Finally we must run the graphql query and refetch + updateProjectComponents({ + variables: { + objects: variablePayload, + }, + }).then(() => exitAndReload()); + }; + + /** + * Handles the deletion of the component + */ + const handleComponentDelete = () => { + deleteProjectComponent({ + variables: { + projComponentId: componentId, + }, + }).then(() => exitAndReload()); + }; + + /** + * Tracks any changes made to selectedComponentId + */ + useEffect(() => { + // If we have data, look to update the selected subcomponents + if (data && selectedComponentType !== null) { + setSelectedSubcomponents([...subcomponentsDB]); + } else { + // If the component id changes, clear out the value of selected subcomponents + setSelectedSubcomponents([]); + } + // eslint-disable-next-line + }, [data, selectedComponentId, selectedComponentType]); + + /** + * Tracks any changes made to the selected type and subtype + */ + useEffect(() => { + // If we don't have a type, then forget about it... + if (selectedComponentType === null) { + setSelectedComponentId(null); + return; + } + + // We have a type, let's check if we need to have a subtype + const subtypeOptional = isSubtypeOptional(selectedComponentType); + + // Exit this function if needed + if (subtypeOptional === false && selectedComponentSubtype === null) { + setSelectedComponentId(null); + return; + } + + // We have what we need, let's get the component id + const newComponentId = getSelectedComponentId( + selectedComponentType, + selectedComponentSubtype + ); + + // Update the selected component id + setSelectedComponentId(newComponentId); + + // eslint-disable-next-line + }, [selectedComponentType, selectedComponentSubtype]); + + if (loading) return ; + if (error) return
Error: {JSON.stringify(error)}
; + + /** + * Returns true if the collection has a minimum of features, false otherwise. + * @type {boolean} + */ + const areMinimumFeaturesSet = + countFeatures(editFeatureCollection) >= mapConfig.minimumFeaturesInProject; + + /** + * Pre-populates the type and subtype for the existing data from DB + */ + if (data && initialTypeCounts && selectedComponentType === null) { + // Get the component_id from the moped_proj_component table or make databaseComponent null + const databaseComponent = + componentId > 0 + ? data.moped_components.filter( + componentItem => + componentItem.component_id === + data.moped_proj_components[0].component_id + )[0] + : null; + + // Determine component type and available subtypes + const componentTypeDB = databaseComponent + ? databaseComponent.component_name.toLowerCase() + : null; + + // Update state with new values + if (componentTypeDB !== selectedComponentType) + setSelectedComponentType(componentTypeDB); + + // If there is a componentId, then get subtypes + if (componentId > 0) { + // Now get the available subtypes + const availableSubTypes = getAvailableSubtypes(componentTypeDB); + setAvailableSubtypes(availableSubTypes); + // If the component type has subtypes, then fetch those and update state + if (initialTypeCounts[componentTypeDB].count > 1) { + const subtypeDB = (databaseComponent?.component_subtype ?? "") + .trim() + .toLowerCase(); + setSelectedComponentSubtype(subtypeDB); + } + } + } + + // Populate component description if available and state is empty + if ( + data && + !!data?.moped_proj_components[0]?.description && + componentDescription === null + ) { + setComponentDescription(data?.moped_proj_components[0]?.description); + } + + /** + * We need to populate the features we will need in order to build the + * geojson collection that we can feed it to the map. + */ + if (data && editFeatureComponents.length === 0) { + // First, we will need a list of all moped_proj_features_components and make it geojson + const featuresFromComponents = ( + data?.moped_proj_components[0]?.moped_proj_features_components ?? [] + ).map(featureComponent => { + // Retrieve the feature component's primary key + const { + moped_proj_features_id, + project_features_components_id, + } = featureComponent; + // Clone the geojson data from the feature component + const newGeoJson = { + ...featureComponent.moped_proj_feature.location, + }; + // Now go ahead and patch the primary key into the GeoJson properties + newGeoJson.properties.project_features_components_id = project_features_components_id; + newGeoJson.properties.moped_proj_feature_id = moped_proj_features_id; + + return newGeoJson; + }); + + /** + * Secondly, use emptyFeatureCollection as a template, then inject our previously rendered features + */ + const featureCollectionFromComponents = { + ...emptyFeatureCollection, + features: featuresFromComponents, + }; + + // Unless we have features to work with, then leave it alone + if (featuresFromComponents.length > 0) { + setEditFeatureComponents(featuresFromComponents); + setEditFeatureCollection(featureCollectionFromComponents); + } + } + + return ( + + + + + + type.toLowerCase() === selectedComponentType + )} + options={availableTypes} + getOptionLabel={component => component} + renderInput={params => ( + + )} + onChange={handleComponentTypeSelect} + /> + + + + {availableSubtypes.length > 0 && ( + + + subtype.toLowerCase() === selectedComponentSubtype + )} + options={[...new Set(availableSubtypes)].sort()} + getOptionLabel={component => component} + renderInput={params => ( + + )} + onChange={handleComponentSubtypeSelect} + /> + + )} + + + + + handleDescriptionKeyDown(e)} + /> + + + {!areMinimumFeaturesSet && ( + + + You must select at least one feature for this component. + + + )} + + + + + + {componentId > 0 && ( + + )} + + + + + + {error && ( + + {mapErrors.failedToSave} + + )} + + + +

{"Delete Component?"}

+
+ + + You cannot undo this operation, any subcomponents and features will + be lost. + + + + + + +
+
+ ); +}; + +export default ProjectComponentEdit; diff --git a/moped-editor/src/views/projects/projectView/ProjectComponentSubcomponents.js b/moped-editor/src/views/projects/projectView/ProjectComponentSubcomponents.js new file mode 100644 index 0000000000..15cb66f7ab --- /dev/null +++ b/moped-editor/src/views/projects/projectView/ProjectComponentSubcomponents.js @@ -0,0 +1,92 @@ +import React from "react"; +import { Chip, FormControl, Grid, TextField } from "@material-ui/core"; +import { Alert, Autocomplete } from "@material-ui/lab"; +import makeStyles from "@material-ui/core/styles/makeStyles"; + +const useStyles = makeStyles(theme => ({ + alert: { + margin: theme.spacing(2), + }, +})); + +/** + * Subcomponent List Tag editor + * @param {Number} componentId - An integer that contains the current compoent id selected + * @param {Object[]} subcomponentList - A list of objects containing all the available subcomponents + * @param {Number[]} selectedSubcomponents - The current state containing a list of selected components + * @param {function} setSelectedSubcomponents - The state setter for the current list state + * @return {JSX.Element} + * @constructor + */ +const ProjectComponentSubcomponents = ({ + componentId, + subcomponentList, + selectedSubcomponents, + setSelectedSubcomponents, +}) => { + const classes = useStyles(); + + // There is nothing to render if the component id is not there + if (componentId === null) + return ( + + You must select a type & subtype. + + ); + + // Count the total number of available subcomponents we have for this component type + const availableSubcomponentsCount = (subcomponentList ?? []).filter( + subComponent => subComponent.component_id === componentId + ).length; + + // If we have zero, then no point to render the autocomplete react component + if (availableSubcomponentsCount === 0) + return ( + + There are no available subcomponents for this component type. + + ); + + return ( + + + { + setSelectedSubcomponents([...newValue]); + }} + options={[ + ...selectedSubcomponents, // We must show existing selected subcomponents, even if there is a mismatch + ...subcomponentList.filter( + // Then append any available subcomponents for the current type + subComponent => subComponent.component_id === componentId + ), + ]} + // The list changes depending on available subcomponents for that specific type + getOptionLabel={option => option.subcomponent_name} + renderTags={(tagValue, getTagProps) => + tagValue.map((option, index) => ( + + )) + } + renderInput={params => ( + + )} + /> + + + ); +}; + +export default ProjectComponentSubcomponents; diff --git a/moped-editor/src/views/projects/projectView/ProjectComponents.js b/moped-editor/src/views/projects/projectView/ProjectComponents.js new file mode 100644 index 0000000000..62286ae076 --- /dev/null +++ b/moped-editor/src/views/projects/projectView/ProjectComponents.js @@ -0,0 +1,279 @@ +import React, { useState } from "react"; +import { useQuery } from "@apollo/client"; +import { useParams } from "react-router-dom"; +import { makeStyles } from "@material-ui/core/styles"; +import { COMPONENTS_QUERY } from "../../../queries/project"; +import { + CardContent, + Grid, + Paper, + Table, + TableBody, + TableContainer, + TableHead, + TableRow, + TableCell, + CircularProgress, + ClickAwayListener, + Icon, + Button, +} from "@material-ui/core"; + +import DoubleArrowIcon from "@material-ui/icons/DoubleArrow"; +import { ErrorBoundary } from "react-error-boundary"; +import ApolloErrorHandler from "../../../components/ApolloErrorHandler"; +import ProjectComponentsMapView from "./ProjectComponentsMapView"; +import { createFeatureCollectionFromProjectFeatures } from "../../../utils/mapHelpers"; +import ProjectSummaryMapFallback from "./ProjectSummaryMapFallback"; +import ProjectComponentEdit from "./ProjectComponentEdit"; +import Alert from "@material-ui/lab/Alert"; + +const useStyles = makeStyles(theme => ({ + root: { + flexGrow: 1, + backgroundColor: theme.palette.background.paper, + }, + componentItem: { + cursor: "pointer", + backgroundColor: theme.palette.background.paper, + "&:hover": { + background: "#f5f5f5", // Gray 50 + }, + }, + componentItemBlue: { + cursor: "pointer", + backgroundColor: "#e1f5fe", // Lightblue 50 + "&:hover": { + background: "#b3e5fc", // Lightblue 100 + }, + }, + componentButtonAddNew: { + marginTop: theme.spacing(2), + }, +})); + +/** + * Project Component Page + * @return {JSX.Element} + * @constructor + */ +const ProjectComponents = () => { + const { projectId } = useParams(); + const classes = useStyles(); + + const [selectedComp, setSelectedComp] = useState(0); + const [mapError, setMapError] = useState(false); + const [componentEditMode, setComponentEditMode] = useState(false); + + const { error, loading, data, refetch } = useQuery(COMPONENTS_QUERY, { + variables: { + projectId: Number.parseInt(projectId), + }, + }); + + // Return loading if not in progress + if (loading) return ; + + if (error) return
{JSON.stringify(error)}
; + + /** + * Retrieve and flatten a nested list of features: + * moped_proj_components -> moped_proj_features_components -> moped_proj_feature + */ + const projectFeatureRecords = data.moped_proj_components.reduce( + (accumulator, component) => [ + ...accumulator, + // Append if current component is selected, or none are selected (0) + ...(selectedComp === component.project_component_id || selectedComp === 0 + ? component.moped_proj_features_components.map(featureComponent => ({ + ...featureComponent.moped_proj_feature, + project_features_components_id: + featureComponent.project_features_components_id, + })) + : []), + ], + [] + ); + + /** + * Reuses this function to generate a collection for the map + * @type {Object} + */ + const projectFeatureCollection = createFeatureCollectionFromProjectFeatures( + projectFeatureRecords + ); + + /** + * Handles logic whenever a component is clicked, refreshes whatever is in memory and re-renders + * @param clickedComponentId - The Database id of the component in question + */ + const handleComponentClick = clickedComponentId => + setSelectedComp(clickedComponentId); + + /** + * Resets the color of a selected component back to white + */ + const handleComponentClickAway = () => setSelectedComp(0); + + /** + * Takes the user to the components details page + */ + const handleComponentDetailsClick = () => { + setComponentEditMode(true); + }; + + /** + * Handles add component button logic + */ + const handleAddNewComponentClick = () => { + setSelectedComp(0); + handleComponentDetailsClick(); + }; + + /** + * Takes the user back to the components list for a project + */ + const handleCancelEdit = () => { + setComponentEditMode(false); + setSelectedComp(0); + }; + + /** + * This is a helper variable that stores true if we have components, false otherwise. + * @constant + * @type {boolean} + */ + const componentsAvailable = data && data.moped_proj_components.length > 0; + + return ( + + + {componentEditMode && ( + + )} + {!componentEditMode && ( + + + {componentsAvailable && ( + + + + + + + Type + Subtype + Sub-Components + Details + + + + {data.moped_proj_components.map( + (component, compIndex) => { + const projComponentId = + component.project_component_id; + return ( + + handleComponentClick(projComponentId) + } + className={ + projComponentId === selectedComp + ? classes.componentItemBlue + : classes.componentItem + } + > + + { + component?.moped_components + ?.component_type + } + + + { + component?.moped_components + ?.component_subtype + } + + + {[ + ...new Set( + component.moped_proj_components_subcomponents.map( + mpcs => + mpcs.moped_subcomponent + .subcomponent_name + ) + ), + ] + .sort() + .join(", ")} + + + + + + ); + } + )} + +
+
+
+
+ )} + {!componentsAvailable && ( + + There aren't any components for this project. + + )} + +
+ {componentsAvailable && ( + + ( + + )} + onReset={() => setMapError(false)} + resetKeys={[mapError]} + > + + + + )} +
+ )} +
+
+ ); +}; + +export default ProjectComponents; diff --git a/moped-editor/src/views/projects/projectView/ProjectComponentsMapView.js b/moped-editor/src/views/projects/projectView/ProjectComponentsMapView.js new file mode 100644 index 0000000000..f2508b6294 --- /dev/null +++ b/moped-editor/src/views/projects/projectView/ProjectComponentsMapView.js @@ -0,0 +1,130 @@ +import React, { useRef } from "react"; +import ReactMapGL, { NavigationControl } from "react-map-gl"; +import { Box, Button, makeStyles } from "@material-ui/core"; +import "mapbox-gl/dist/mapbox-gl.css"; +import "react-map-gl-geocoder/dist/mapbox-gl-geocoder.css"; + +import { + createSummaryMapLayers, + getSummaryMapInteractiveIds, + MAPBOX_TOKEN, + mapStyles, + renderTooltip, + renderFeatureCount, + countFeatures, + useHoverLayer, + useFeatureCollectionToFitBounds, +} from "../../../utils/mapHelpers"; +import { EditLocation as EditLocationIcon } from "@material-ui/icons"; + +const useStyles = makeStyles({ + locationCountText: { + fontSize: "0.875rem", + fontWeight: 500, + }, + toolTip: mapStyles.toolTipStyles, + navStyle: { + position: "absolute", + top: 0, + left: 0, + padding: "10px", + }, + editButton: { + position: "absolute", + top: 8, + right: 8, + }, +}); + +const ProjectComponentsMapView = ({ + projectFeatureCollection, + setIsEditing, + editEnabled, +}) => { + const classes = useStyles(); + const mapRef = useRef(); + const featureCount = countFeatures(projectFeatureCollection); + + /** + * Make use of a custom hook that returns a vector tile layer hover event handler + * and the details to place and populate a tooltip. + */ + const { handleLayerHover, featureText, hoveredCoords } = useHoverLayer(); + + /** + * Make use of a custom hook that initializes a map viewport + * and fits it to a provided feature collection. + */ + const [viewport, setViewport] = useFeatureCollectionToFitBounds( + mapRef, + projectFeatureCollection + ); + + /** + * Updates viewport on zoom, scroll, and other events + * @param {Object} updatedViewPort - Mapbox object that stores properties of the map view + */ + const handleViewportChange = updatedViewPort => setViewport(updatedViewPort); + + /** + * Let's throw an error intentionally if there are no features for a project. + */ + if (featureCount < 1) { + throw Error("Map error: Cannot render or edit maps with no features"); + } + + /** + * If we do have features, proceed to render map. + */ + return ( + + + {/* Draw Navigation controls with specific styles */} +
+ +
+ {/* + If there is GeoJSON data, create sources and layers for + each source layer in the project's GeoJSON FeatureCollection + */} + {projectFeatureCollection && + createSummaryMapLayers(projectFeatureCollection)} + {/* Draw tooltip on feature hover */} + {renderTooltip(featureText, hoveredCoords, classes.toolTip)} + {editEnabled && ( + + )} +
+ {renderFeatureCount(featureCount)} +
+ ); +}; + +export default ProjectComponentsMapView; diff --git a/moped-editor/src/views/projects/projectView/ProjectSummary.js b/moped-editor/src/views/projects/projectView/ProjectSummary.js index 021331c5be..ce52fb433c 100644 --- a/moped-editor/src/views/projects/projectView/ProjectSummary.js +++ b/moped-editor/src/views/projects/projectView/ProjectSummary.js @@ -18,6 +18,7 @@ import { ErrorBoundary } from "react-error-boundary"; const ProjectSummary = ({ loading, error, data, refetch }) => { const { projectId } = useParams(); + const [makeSureRefresh, setMakeSureRefresh] = useState(false); const [isEditing, setIsEditing] = useState(false); const [mapError, setMapError] = useState(false); @@ -29,6 +30,10 @@ const ProjectSummary = ({ loading, error, data, refetch }) => { projectFeatureRecords ); + refetch(); + if (projectFeatureRecords.length === 0 && !makeSureRefresh) + setMakeSureRefresh(true); + return ( @@ -44,7 +49,7 @@ const ProjectSummary = ({ loading, error, data, refetch }) => { {projectFeatureCollection && ( ( + FallbackComponent={({ error, resetErrorBoundary }) => ( { +const ProjectSummaryMap = ({ + projectExtentGeoJSON, + setIsEditing, + isEditable = false, +}) => { const classes = useStyles(); const mapRef = useRef(); const featureCount = countFeatures(projectExtentGeoJSON); @@ -65,7 +69,7 @@ const ProjectSummaryMap = ({ projectExtentGeoJSON, setIsEditing }) => { /** * Let's throw an error intentionally if there are no features for a project. */ - if(featureCount < 1) { + if (featureCount < 1) { throw Error("Map error: Cannot render or edit maps with no features"); } @@ -104,15 +108,17 @@ const ProjectSummaryMap = ({ projectExtentGeoJSON, setIsEditing }) => { {/* Draw tooltip on feature hover */} {renderTooltip(featureText, hoveredCoords, classes.toolTip)} {/* Draw edit button controls with specific styles */} - + {isEditable && ( + + )} {renderFeatureCount(featureCount)} diff --git a/moped-editor/src/views/projects/projectView/ProjectSummaryMapFallback.js b/moped-editor/src/views/projects/projectView/ProjectSummaryMapFallback.js index cbd1463a05..d709f52905 100644 --- a/moped-editor/src/views/projects/projectView/ProjectSummaryMapFallback.js +++ b/moped-editor/src/views/projects/projectView/ProjectSummaryMapFallback.js @@ -1,9 +1,8 @@ import React from "react"; import { Box, Button, Card, Icon, makeStyles } from "@material-ui/core"; -import { gql, useMutation } from "@apollo/client"; -import { PROJECT_CLEAR_MAP_DATA_TEMPLATE } from "../../../queries/project"; -import { v4 as uuid } from "uuid"; + +import { Link } from "react-router-dom"; const useStyles = makeStyles(theme => ({ root: { @@ -35,50 +34,19 @@ const useStyles = makeStyles(theme => ({ * Renders a fallback component that shows the user whenever there is a map error. * @param {object} error - provided by ErrorBoundary component, contains error details. * @param {function} resetErrorBoundary - A function that forces ErrorBoundary to re-render it's children components - * @param {integer} projectId - The project id of the map - * @param {function} refetchProjectDetails - A refetch function to run after the data is cleared - * @param {function} setIsEditing - A function to enable editing * @param {object} mapData - The map data with errors to show in the console for debugging * @return {JSX.Element} * @constructor */ -const ProjectSummaryMapFallback = ({ - error, - resetErrorBoundary, - projectId, - refetchProjectDetails, - setIsEditing, - mapData, -}) => { +const ProjectSummaryMapFallback = ({ error, mapData }) => { const classes = useStyles(); - const [clearProjectMapData] = useMutation( - gql(PROJECT_CLEAR_MAP_DATA_TEMPLATE.replace("RANDOM_FEATURE_ID", uuid())), - { - variables: { - projectId: projectId, - }, - } - ); - /** * Log whatever error there may be */ console.error("MapDataError: ", error); console.error("MapData: ", mapData); - /** - * Clears the json data in the project and opens the editor - */ - const clearAndEdit = () => { - clearProjectMapData().then(() => { - refetchProjectDetails().then(() => { - setIsEditing(true); - resetErrorBoundary(); - }); - }); - }; - return ( @@ -90,8 +58,8 @@ const ProjectSummaryMapFallback = ({

No map available

- The map for this project is either missing or outdated. Create a new - map or{" "} + This project does not have components or is outdated. Create new + components or{" "}

- + + +
); diff --git a/moped-editor/src/views/projects/projectView/ProjectView.js b/moped-editor/src/views/projects/projectView/ProjectView.js index f233629dac..82595ac86f 100644 --- a/moped-editor/src/views/projects/projectView/ProjectView.js +++ b/moped-editor/src/views/projects/projectView/ProjectView.js @@ -32,6 +32,7 @@ import { import Page from "src/components/Page"; import ProjectSummary from "./ProjectSummary"; +import ProjectComponents from "./ProjectComponents"; import ProjectTeam from "./ProjectTeam"; import ProjectTimeline from "./ProjectTimeline"; import ProjectComments from "./ProjectComments"; @@ -96,6 +97,7 @@ function useQueryParams() { const TABS = [ { label: "Summary", Component: ProjectSummary, param: "summary" }, + { label: "Map", Component: ProjectComponents, param: "map" }, { label: "Files", Component: ProjectFiles, param: "files" }, { label: "Team", Component: ProjectTeam, param: "team" }, { label: "Timeline", Component: ProjectTimeline, param: "timeline" }, @@ -159,6 +161,7 @@ const ProjectView = () => { */ const { loading, error, data, refetch } = useQuery(SUMMARY_QUERY, { variables: { projectId }, + fetchPolicy: "no-cache", }); /**