diff --git a/featureflags/graph/actions.py b/featureflags/graph/actions.py index 0370db1..cad6363 100644 --- a/featureflags/graph/actions.py +++ b/featureflags/graph/actions.py @@ -504,28 +504,80 @@ async def delete_variable( @auth_required @track async def delete_project( - project_id: str, + project_id: UUID, *, conn: SAConnection, ) -> None: + """Physycally deletes project and its: + * flags + * variables + * conditions + * checks + """ assert project_id, "Project id is required" - project_uuid = UUID(hex=project_id) + # Delete flag conditions + flags = await conn.execute( + select([Flag.id]).where(Flag.project == project_id) + ) + flag_ids = [f.id for f in await flags.fetchall()] + + await conn.execute( + Condition.__table__.delete().where(Condition.flag.in_(flag_ids)) + ) + + # Delete flags changelog + await conn.execute( + Changelog.__table__.delete().where(Changelog.flag.in_(flag_ids)) + ) + + # Delete project flags + await conn.execute( + Flag.__table__.delete().where(Flag.project == project_id) + ) + + # Delete value conditions + values = await conn.execute( + select([Value.id]).where(Value.project == project_id) + ) + values_ids = [v.id for v in await values.fetchall()] + + await conn.execute( + ValueCondition.__table__.delete().where( + ValueCondition.value.in_(values_ids) + ) + ) + + # Delete value changelog + await conn.execute( + ValueChangelog.__table__.delete().where( + ValueChangelog.value.in_(values_ids) + ) + ) + + # Delete project values + await conn.execute( + Flag.__table__.delete().where(Flag.project == project_id) + ) + # Delete project variables variables = await conn.execute( - select([Variable.id]).where(Project.id == project_uuid) + select([Variable.id]).where(Project.id == project_id) ) variable_ids = [v.id for v in await variables.fetchall()] + # Delete variable checks if variable_ids: await conn.execute( Check.__table__.delete().where(Check.variable.in_(variable_ids)) ) + # Delete variables await conn.execute( - Variable.__table__.delete().where(Variable.project == project_uuid) + Variable.__table__.delete().where(Variable.project == project_id) ) + # Finally delete project await conn.execute( - Project.__table__.delete().where(Project.id == project_uuid) + Project.__table__.delete().where(Project.id == project_id) ) diff --git a/featureflags/graph/graph.py b/featureflags/graph/graph.py index 8cbdbb2..6ecfd68 100644 --- a/featureflags/graph/graph.py +++ b/featureflags/graph/graph.py @@ -1187,23 +1187,9 @@ async def delete_project(ctx: dict, options: dict) -> DeleteProjectResult: async with ctx[GraphContext.DB_ENGINE].acquire() as conn: project_uuid = UUID(options["id"]) - is_flags_exists = await exec_scalar( - ctx[GraphContext.DB_ENGINE], - (select([Flag.id]).where(Flag.project == project_uuid).limit(1)), - ) - if is_flags_exists: - return DeleteProjectResult("You need delete all Flags firstly.") - - is_values_exists = await exec_scalar( - ctx[GraphContext.DB_ENGINE], - (select([Value.id]).where(Value.project == project_uuid).limit(1)), - ) - if is_values_exists: - return DeleteProjectResult("You need delete all Values firstly.") - try: await actions.delete_project( - options["id"], + project_uuid, conn=conn, ) except Exception as e: diff --git a/featureflags/graph/proto_adapter.py b/featureflags/graph/proto_adapter.py index 9b5c926..539fa23 100644 --- a/featureflags/graph/proto_adapter.py +++ b/featureflags/graph/proto_adapter.py @@ -85,8 +85,7 @@ def variable(self, obj, value): obj.variable.Variable = value.ident.hex def operator(self, obj, value): - if value is not None: - obj.operator = value.to_pb() + obj.operator = value.to_pb() def value_string(self, obj, value): if value is not None: diff --git a/featureflags/migrations/versions/2fa54f8b55c1_cleaup_condition_checks.py b/featureflags/migrations/versions/2fa54f8b55c1_cleaup_condition_checks.py new file mode 100644 index 0000000..f50674e --- /dev/null +++ b/featureflags/migrations/versions/2fa54f8b55c1_cleaup_condition_checks.py @@ -0,0 +1,36 @@ +from alembic import op +from sqlalchemy.sql import text + +revision = "2fa54f8b55c1" +down_revision = "a327a3ea7a5f" +branch_labels = None +depends_on = None + + +def upgrade(): + conn = op.get_bind() + + conditions = list(conn.execute("SELECT id, checks FROM condition")) + checks = list(conn.execute('SELECT id FROM "check"')) + + print("Found conditions: {}".format(len(conditions))) + print("Found checks: {}".format(len(checks))) + + check_ids = {check.id for check in checks} + + for condition in conditions: + new_checks = {check for check in condition.checks if check in check_ids} + if set(condition.checks) != new_checks: + print( + "Updating condition {} with new checks: before={}, after={}".format( + condition.id, condition.checks, new_checks + ) + ) + + conn.execute( + text("UPDATE condition SET checks = :checks WHERE id = :id"), + {"checks": list(new_checks), "id": condition.id}, + ) + + +def downgrade(): ... diff --git a/featureflags/rpc/servicer.py b/featureflags/rpc/servicer.py index d293ba8..be93947 100644 --- a/featureflags/rpc/servicer.py +++ b/featureflags/rpc/servicer.py @@ -93,26 +93,7 @@ async def Exchange(self, stream: Stream) -> None: # noqa: N802 db_engine=self._db_engine, session=user_session.get(), ) - try: - log.debug( - "Graph result: %r", - result.__idx__, - extra={ - "project_name": request.project, - "request_verion": request.version, - "project_version": version, - }, - ) - populate_result_proto(result, exchange_reply.result) - except: # noqa - log.error( - "Failed to populate result proto", - extra={ - "project_name": request.project, - "request_verion": request.version, - "project_version": version, - }, - ) + populate_result_proto(result, exchange_reply.result) exchange_reply.version = version diff --git a/lets.yaml b/lets.yaml index 392d1e8..9db3759 100644 --- a/lets.yaml +++ b/lets.yaml @@ -113,7 +113,7 @@ commands: depends: [build-dev] cmd: | docker-compose run --rm backend \ - pdm run python3 -m featureflags alembic -- revision --autogenerate -m + pdm run python3 -m featureflags alembic -- revision --autogenerate -m ${LETS_COMMAND_ARGS} apply-seeds-dev: description: Apply seeds to local postgres