From c78ac6337b1411c0ecb6957edbca07d6f0b76b9a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 5 Sep 2024 12:09:05 +0300 Subject: [PATCH] Added feature values (#38) * Added feature values --------- Co-authored-by: d.maximchuk --- featureflags/__init__.py | 2 +- featureflags/graph/actions.py | 207 +++ featureflags/graph/context.py | 8 +- featureflags/graph/graph.py | 431 +++++ featureflags/graph/types.py | 73 + featureflags/http/app.py | 2 +- featureflags/http/db.py | 77 +- featureflags/http/repositories/flags.py | 39 +- featureflags/http/types.py | 18 + ...876f90b58e8_added_feature_values_tables.py | 107 ++ featureflags/models.py | 46 +- featureflags/rpc/app.py | 2 +- featureflags/services/ldap.py | 2 +- featureflags/tests/conftest.py | 7 +- featureflags/tests/state.py | 71 + featureflags/tests/test_actions.py | 334 ++++ featureflags/tests/test_graph.py | 246 ++- featureflags/tests/test_web.py | 116 +- featureflags/utils.py | 12 + featureflags/web/app.py | 2 +- pyproject.toml | 1 + ui/package-lock.json | 1517 +++++++++-------- ui/src/Base.jsx | 20 +- ui/src/Dashboard/Dashboard.jsx | 20 +- ui/src/Dashboard/Flag.jsx | 20 +- ui/src/Dashboard/Value.jsx | 455 +++++ ui/src/Dashboard/Value.less | 7 + ui/src/Dashboard/ValueCheck.jsx | 145 ++ ui/src/Dashboard/ValueConditions.jsx | 86 + ui/src/Dashboard/ValueConditions.less | 6 + ui/src/Dashboard/Values.jsx | 161 ++ ui/src/Dashboard/Values.less | 11 + ui/src/Dashboard/actions.js | 264 ++- ui/src/Dashboard/context.jsx | 21 + ui/src/Dashboard/queries.js | 71 + ui/src/Dashboard/utils.js | 16 + 36 files changed, 3860 insertions(+), 763 deletions(-) create mode 100644 featureflags/migrations/versions/1876f90b58e8_added_feature_values_tables.py create mode 100644 ui/src/Dashboard/Value.jsx create mode 100644 ui/src/Dashboard/Value.less create mode 100644 ui/src/Dashboard/ValueCheck.jsx create mode 100644 ui/src/Dashboard/ValueConditions.jsx create mode 100644 ui/src/Dashboard/ValueConditions.less create mode 100644 ui/src/Dashboard/Values.jsx create mode 100644 ui/src/Dashboard/Values.less create mode 100644 ui/src/Dashboard/utils.js diff --git a/featureflags/__init__.py b/featureflags/__init__.py index e13bd59..6849410 100644 --- a/featureflags/__init__.py +++ b/featureflags/__init__.py @@ -1 +1 @@ -__version__ = "1.0.8" +__version__ = "1.1.0" diff --git a/featureflags/graph/actions.py b/featureflags/graph/actions.py index dc7ce58..3ee84ab 100644 --- a/featureflags/graph/actions.py +++ b/featureflags/graph/actions.py @@ -11,9 +11,12 @@ Action, AddCheckOp, AddConditionOp, + AddValueConditionOp, Changes, DirtyProjects, LocalId, + ValueAction, + ValuesChanges, ) from featureflags.graph.utils import gen_id, get_auth_user, update_map from featureflags.models import ( @@ -24,6 +27,9 @@ Flag, Operator, Project, + Value, + ValueChangelog, + ValueCondition, Variable, ) from featureflags.services.auth import UserSession, auth_required @@ -251,6 +257,8 @@ async def postprocess(*, conn: SAConnection, dirty: DirtyProjects) -> None: selections.append( select([Variable.project]).where(Variable.id == variable_id) ) + for value_id in dirty.by_value: + selections.append(select([Value.project]).where(Value.id == value_id)) if selections: await conn.execute( update(Project.__table__) @@ -277,3 +285,202 @@ async def update_changelog( } ) ) + + +@auth_required +@track +async def enable_value( + value_id: str, + *, + conn: SAConnection, + dirty: DirtyProjects, + changes: ValuesChanges, +) -> None: + assert value_id, "Value id is required" + + value_uuid = UUID(hex=value_id) + await conn.execute( + Value.__table__.update() + .where(Value.id == value_uuid) + .values({Value.enabled: True}) + ) + dirty.by_value.add(value_uuid) + changes.add(value_uuid, ValueAction.ENABLE_VALUE) + + +@auth_required +@track +async def update_value_value_override( + value_id: str, + value_override: str, + *, + conn: SAConnection, + dirty: DirtyProjects, + changes: ValuesChanges, +) -> None: + assert value_id, "Value id is required" + + value_uuid = UUID(hex=value_id) + await conn.execute( + Value.__table__.update() + .where(Value.id == value_uuid) + .values({Value.value_override: value_override}) + ) + dirty.by_value.add(value_uuid) + changes.add(value_uuid, ValueAction.UPDATE_VALUE_VALUE_OVERRIDE) + + +@auth_required +@track +async def disable_value( + value_id: str, + *, + conn: SAConnection, + dirty: DirtyProjects, + changes: ValuesChanges, +) -> None: + assert value_id, "Value id is required" + + value_uuid = UUID(hex=value_id) + await conn.execute( + Value.__table__.update() + .where(Value.id == value_uuid) + .values({Value.enabled: False}) + ) + dirty.by_value.add(value_uuid) + changes.add(value_uuid, ValueAction.DISABLE_VALUE) + + +@auth_required +@track +async def reset_value( + value_id: str, + *, + conn: SAConnection, + dirty: DirtyProjects, + changes: ValuesChanges, +) -> None: + assert value_id, "Value id is required" + + value_uuid = UUID(hex=value_id) + + result = await conn.execute( + select([Value.value_default]).where(Value.id == value_uuid) + ) + value_default = await result.scalar() + + await conn.execute( + Value.__table__.update() + .where(Value.id == value_uuid) + .values( + { + Value.value_override: value_default, + Value.enabled: None, + } + ) + ) + await conn.execute( + ValueCondition.__table__.delete().where( + ValueCondition.value == value_uuid + ) + ) + dirty.by_value.add(value_uuid) + changes.add(value_uuid, ValueAction.RESET_VALUE) + + +@auth_required +@track +async def delete_value( + value_id: str, *, conn: SAConnection, changes: ValuesChanges +) -> None: + assert value_id, "Value id is required" + + value_uuid = UUID(hex=value_id) + await conn.execute( + ValueCondition.__table__.delete().where( + ValueCondition.value == value_uuid + ) + ) + await conn.execute(Value.__table__.delete().where(Value.id == value_uuid)) + + changes.add(value_uuid, ValueAction.DELETE_VALUE) + + +@auth_required +@track +async def add_value_condition( + op: AddValueConditionOp, + *, + conn: SAConnection, + ids: dict, + value_override: str, + dirty: DirtyProjects, + changes: ValuesChanges, +) -> dict: + id_ = await gen_id(op.local_id, conn=conn) + + value_id = UUID(hex=op.value_id) + checks = [ + ids[check.local_id] if check.local_id else UUID(hex=check.id) + for check in op.checks + ] + + await conn.execute( + insert(ValueCondition.__table__) + .values( + { + ValueCondition.id: id_, + ValueCondition.value: value_id, + ValueCondition.checks: checks, + ValueCondition.value_override: value_override, + } + ) + .on_conflict_do_nothing() + ) + dirty.by_value.add(value_id) + changes.add(value_id, ValueAction.ADD_CONDITION) + return update_map(ids, {op.local_id: id_}) + + +@auth_required +@track +async def disable_value_condition( + condition_id: str, + *, + conn: SAConnection, + dirty: DirtyProjects, + changes: ValuesChanges, +) -> None: + assert condition_id, "Condition id is required" + + value_id = await select_scalar( + conn, + ( + ValueCondition.__table__.delete() + .where(ValueCondition.id == UUID(hex=condition_id)) + .returning(ValueCondition.value) + ), + ) + if value_id is not None: + dirty.by_value.add(value_id) + changes.add(value_id, ValueAction.DISABLE_CONDITION) + + +async def update_value_changelog( + *, session: UserSession, conn: SAConnection, changes: ValuesChanges +) -> None: + actions = changes.get_actions() + if actions: + assert session.user is not None + for value, value_actions in actions: + assert value_actions, repr(value_actions) + await conn.execute( + insert(ValueChangelog.__table__).values( + { + ValueChangelog.timestamp: datetime.utcnow(), + ValueChangelog.auth_user: session.user, + ValueChangelog.value: value, + ValueChangelog.actions: value_actions, + } + ) + ) diff --git a/featureflags/graph/context.py b/featureflags/graph/context.py index e47561e..ae297bd 100644 --- a/featureflags/graph/context.py +++ b/featureflags/graph/context.py @@ -1,6 +1,11 @@ import aiopg.sa -from featureflags.graph.types import Changes, DirtyProjects, GraphContext +from featureflags.graph.types import ( + Changes, + DirtyProjects, + GraphContext, + ValuesChanges, +) from featureflags.services.auth import BaseUserSession from featureflags.services.ldap import BaseLDAP @@ -16,5 +21,6 @@ def init_graph_context( GraphContext.LDAP_SERVICE: ldap, GraphContext.DIRTY_PROJECTS: DirtyProjects(), GraphContext.CHANGES: Changes(), + GraphContext.VALUES_CHANGES: ValuesChanges(), GraphContext.CHECK_IDS: {}, } diff --git a/featureflags/graph/graph.py b/featureflags/graph/graph.py index 8c0b16a..0958d30 100644 --- a/featureflags/graph/graph.py +++ b/featureflags/graph/graph.py @@ -43,12 +43,16 @@ from featureflags.graph.types import ( AddCheckOp, AddConditionOp, + AddValueConditionOp, AuthResult, DeleteFlagResult, + DeleteValueResult, GraphContext, Operation, ResetFlagResult, + ResetValueResult, SaveFlagResult, + SaveValueResult, ) from featureflags.graph.utils import is_valid_uuid from featureflags.metrics import wrap_metric @@ -59,6 +63,9 @@ Condition, Flag, Project, + Value, + ValueChangelog, + ValueCondition, Variable, ) from featureflags.services.auth import UserSession @@ -125,6 +132,55 @@ async def root_flags_by_ids(ctx: dict, options: dict) -> list: return [] +@pass_context +async def root_value(ctx: dict, options: dict) -> list: + if not ( + ctx[GraphContext.USER_SESSION].is_authenticated + or not is_valid_uuid(options["id"]) + ): + return Nothing + + value = await exec_scalar( + ctx[GraphContext.DB_ENGINE], + select([Value.id]).where(Value.id == UUID(options["id"])), + ) + + return value or Nothing + + +@pass_context +async def root_values(ctx: dict, options: dict) -> list: + if not ctx[GraphContext.USER_SESSION].is_authenticated: + return [] + + project_name = options.get("project_name") + expr = select([Value.id]) + + if project_name is not None: + expr = expr.where( + Value.project.in_( + select([Project.id]).where(Project.name == project_name) + ) + ) + + return await exec_expression(ctx[GraphContext.DB_ENGINE], expr) + + +@pass_context +async def root_values_by_ids(ctx: dict, options: dict) -> list: + if not ctx[GraphContext.USER_SESSION].is_authenticated: + return [] + + ids = list(filter(is_valid_uuid, options["ids"])) + if ids: + return await exec_expression( + ctx[GraphContext.DB_ENGINE], + select([Value.id]).where(Value.id.in_(ids)), + ) + + return [] + + @pass_context async def root_projects(ctx: dict) -> list: if not ctx[GraphContext.USER_SESSION].is_authenticated: @@ -155,6 +211,27 @@ async def root_changes(ctx: dict, options: dict) -> list: ) +@pass_context +async def root_values_changes(ctx: dict, options: dict) -> list: + if not ctx[GraphContext.USER_SESSION].is_authenticated: + return [] + + project_ids = options.get("project_ids") + sel = select([ValueChangelog.id]) + if project_ids is not None: + if not project_ids: + return [] + join = ValueChangelog.__table__.join( + Value.__table__, ValueChangelog.value == Value.id + ) + sel = sel.select_from(join).where(Value.project.in_(project_ids)) + + return await exec_expression( + ctx[GraphContext.DB_ENGINE], + sel.order_by(ValueChangelog.timestamp.desc()), + ) + + @pass_context async def root_authenticated(ctx: dict, _options: dict) -> list: return [ctx[GraphContext.USER_SESSION].is_authenticated] @@ -168,6 +245,10 @@ async def flag_project(ids: list[int]) -> list[int]: return ids +async def value_project(ids: list[int]) -> list[int]: + return ids + + ID_FIELD = Field("id", None, id_field) flag_fq = FieldsQuery(GraphContext.DB_ENGINE, Flag.__table__) @@ -182,6 +263,20 @@ async def flag_project(ids: list[int]) -> list[int]: ], ) +value_fq = FieldsQuery(GraphContext.DB_ENGINE, Value.__table__) + +_ValueNode = Node( + "Value", + [ + ID_FIELD, + Field("name", None, value_fq), + Field("project", None, value_fq), + Field("enabled", None, value_fq), + Field("value_default", None, value_fq), + Field("value_override", None, value_fq), + ], +) + condition_fq = FieldsQuery(GraphContext.DB_ENGINE, Condition.__table__) _ConditionNode = Node( @@ -192,6 +287,20 @@ async def flag_project(ids: list[int]) -> list[int]: ], ) +value_condition_fq = FieldsQuery( + GraphContext.DB_ENGINE, + ValueCondition.__table__, +) + +_ValueConditionNode = Node( + "ValueCondition", + [ + ID_FIELD, + Field("checks", None, value_condition_fq), + Field("value_override", None, value_condition_fq), + ], +) + check_fq = FieldsQuery(GraphContext.DB_ENGINE, Check.__table__) _CheckNode = Node( @@ -219,12 +328,30 @@ async def flag_project(ids: list[int]) -> list[int]: ], ) +value_changelog_fq = FieldsQuery( + GraphContext.DB_ENGINE, + ValueChangelog.__table__, +) + +_ValueChangeNode = Node( + "ValueChange", + [ + Field("timestamp", None, value_changelog_fq), + Field("actions", None, value_changelog_fq), + Field("auth_user", None, value_changelog_fq), + Field("value", None, value_changelog_fq), + ], +) + _GRAPH = Graph( [ _FlagNode, + _ValueNode, _ConditionNode, + _ValueConditionNode, _CheckNode, _ChangeNode, + _ValueChangeNode, ] ) _GRAPH = apply(_GRAPH, [AsyncGraphMetrics("source")]) @@ -289,6 +416,42 @@ async def flag_project(ids: list[int]) -> list[int]: ], ) +value_sg = SubGraph(_GRAPH, "Value") + +value_conditions = LinkQuery( + GraphContext.DB_ENGINE, + from_column=ValueCondition.value, + to_column=ValueCondition.id, +) + +ValueNode = Node( + "Value", + [ + ID_FIELD, + Field("name", None, value_sg), + Field("_project", None, value_sg.c(S.this.project)), + Link("project", TypeRef["Project"], value_project, requires="_project"), + Field( + "enabled", + None, + value_sg.c(if_some([S.enabled, S.this.enabled], S.enabled, False)), + ), + Link( + "conditions", + Sequence["ValueCondition"], + value_conditions, + requires="id", + ), + Field( + "overridden", + None, + value_sg.c(if_some([S.enabled, S.this.enabled], True, False)), + ), + Field("value_default", None, value_sg), + Field("value_override", None, value_sg), + ], +) + condition_sg = SubGraph(_GRAPH, "Condition") ConditionNode = Node( @@ -300,6 +463,18 @@ async def flag_project(ids: list[int]) -> list[int]: ], ) +value_condition_sg = SubGraph(_GRAPH, "ValueCondition") + +ValueConditionNode = Node( + "ValueCondition", + [ + ID_FIELD, + Field("_checks", None, value_condition_sg.c(S.this.checks)), + Link("checks", Sequence["Check"], direct_link, requires="_checks"), + Field("value_override", String, value_condition_sg), + ], +) + check_sg = SubGraph(_GRAPH, "Check") CheckNode = Node( @@ -346,6 +521,21 @@ async def flag_project(ids: list[int]) -> list[int]: ], ) +value_change_sg = SubGraph(_GRAPH, "ValueChange") + +ValueChangeNode = Node( + "ValueChange", + [ + ID_FIELD, + Field("timestamp", None, value_change_sg), + Field("_user", None, value_change_sg.c(S.this.auth_user)), + Field("_value", None, value_change_sg.c(S.this.value)), + Field("actions", None, value_change_sg), + Link("value", TypeRef["Value"], direct_link, requires="_value"), + Link("user", TypeRef["User"], direct_link, requires="_user"), + ], +) + RootNode = Root( [ Link( @@ -369,6 +559,27 @@ async def flag_project(ids: list[int]) -> list[int]: requires=None, options=[Option("ids", Sequence[String])], ), + Link( + "value", + Optional["Value"], + root_value, + requires=None, + options=[Option("id", String)], + ), + Link( + "values", + Sequence["Value"], + root_values, + requires=None, + options=[Option("project_name", Optional[String], default=None)], + ), + Link( + "values_by_ids", + Sequence["Value"], + root_values_by_ids, + requires=None, + options=[Option("ids", Sequence[String])], + ), Link("projects", Sequence["Project"], root_projects, requires=None), Link( "changes", @@ -379,6 +590,15 @@ async def flag_project(ids: list[int]) -> list[int]: Option("project_ids", Optional[Sequence[String]], default=None) ], ), + Link( + "valueChanges", + Sequence["ValueChange"], + root_values_changes, + requires=None, + options=[ + Option("project_ids", Optional[Sequence[String]], default=None) + ], + ), Field("authenticated", Boolean, root_authenticated), ] ) @@ -456,6 +676,49 @@ def get_field(name: str) -> str | None: ) +async def save_value_info( + fields: list[Field], results: list[SaveValueResult] +) -> list[list]: + [result] = results + + def get_field(name: str) -> list[str] | None: + if name == "errors": + return result.errors + + raise ValueError(f"Unknown field: {name}") + + return [[get_field(f.name)] for f in fields] + + +async def reset_value_info( + fields: list[Field], results: list[ResetValueResult] +) -> list[list]: + [result] = results + + def get_field(name: str) -> str | None: + if name == "error": + return result.error + + raise ValueError(f"Unknown field: {name}") + + return [[get_field(f.name)] for f in fields] + + +SaveValueNode = Node( + "SaveValue", + [ + Field("errors", None, save_value_info), + ], +) + +ResetValueNode = Node( + "ResetValue", + [ + Field("error", None, reset_value_info), + ], +) + + async def delete_flag_info( fields: list[Field], results: list[DeleteFlagResult] ) -> list[list]: @@ -477,15 +740,40 @@ def get_field(name: str) -> str | None: ], ) + +async def delete_value_info( + fields: list[Field], results: list[DeleteValueResult] +) -> list[list]: + [result] = results + + def get_field(name: str) -> str | None: + if name == "error": + return result.error + + raise ValueError(f"Unknown field: {name}") + + return [[get_field(f.name)] for f in fields] + + +DeleteValueNode = Node( + "DeleteValue", + [ + Field("error", None, delete_flag_info), + ], +) + GRAPH = Graph( [ ProjectNode, VariableNode, FlagNode, + ValueNode, ConditionNode, + ValueConditionNode, CheckNode, UserNode, ChangeNode, + ValueChangeNode, RootNode, ] ) @@ -628,8 +916,122 @@ async def delete_flag(ctx: dict, options: dict) -> DeleteFlagResult: return DeleteFlagResult(None) +@pass_context +async def save_value(ctx: dict, options: dict) -> SaveValueResult: + operations = options["operations"] + + if not operations: + return SaveValueResult(None) + + async with ctx[GraphContext.DB_ENGINE].acquire() as conn: + for operation in operations: + operation_payload = operation["payload"] + operation_type = Operation(operation["type"]) + + match operation_type: + case Operation.ENABLE_VALUE: + await actions.enable_value( + operation_payload["value_id"], + conn=conn, + dirty=ctx[GraphContext.DIRTY_PROJECTS], + changes=ctx[GraphContext.VALUES_CHANGES], + ) + case Operation.UPDATE_VALUE_VALUE_OVERRIDE: + value_override = operation["payload"]["value_override"] + await actions.update_value_value_override( + operation_payload["value_id"], + value_override=str(value_override), + conn=conn, + dirty=ctx[GraphContext.DIRTY_PROJECTS], + changes=ctx[GraphContext.VALUES_CHANGES], + ) + case Operation.DISABLE_VALUE: + await actions.disable_value( + operation_payload["value_id"], + conn=conn, + dirty=ctx[GraphContext.DIRTY_PROJECTS], + changes=ctx[GraphContext.VALUES_CHANGES], + ) + case Operation.ADD_CHECK: + new_ids = await actions.add_check( + AddCheckOp(operation_payload), + conn=conn, + dirty=ctx[GraphContext.DIRTY_PROJECTS], + ) + if new_ids is not None: + ctx[GraphContext.CHECK_IDS].update(new_ids) + case Operation.ADD_VALUE_CONDITION: + value_override = operation_payload[ + "value_condition_override" + ] + new_ids = await actions.add_value_condition( + AddValueConditionOp(operation_payload), + conn=conn, + ids=ctx[GraphContext.CHECK_IDS], + value_override=str(value_override), + dirty=ctx[GraphContext.DIRTY_PROJECTS], + changes=ctx[GraphContext.VALUES_CHANGES], + ) + if new_ids is not None: + ctx[GraphContext.CHECK_IDS].update(new_ids) + case Operation.DISABLE_VALUE_CONDITION: + await actions.disable_value_condition( + operation_payload["condition_id"], + conn=conn, + dirty=ctx[GraphContext.DIRTY_PROJECTS], + changes=ctx[GraphContext.VALUES_CHANGES], + ) + case _: + raise ValueError(f"Unknown operation: {operation_type}") + + await actions.postprocess( + conn=conn, dirty=ctx[GraphContext.DIRTY_PROJECTS] + ) + await actions.update_value_changelog( + session=ctx[GraphContext.USER_SESSION], + conn=conn, + changes=ctx[GraphContext.VALUES_CHANGES], + ) + + return SaveValueResult(None) + + +@pass_context +async def reset_value(ctx: dict, options: dict) -> ResetValueResult: + async with ctx[GraphContext.DB_ENGINE].acquire() as conn: + await actions.reset_value( + options["id"], + conn=conn, + dirty=ctx[GraphContext.DIRTY_PROJECTS], + changes=ctx[GraphContext.VALUES_CHANGES], + ) + await actions.postprocess( + conn=conn, dirty=ctx[GraphContext.DIRTY_PROJECTS] + ) + await actions.update_value_changelog( + session=ctx[GraphContext.USER_SESSION], + conn=conn, + changes=ctx[GraphContext.VALUES_CHANGES], + ) + + return ResetValueResult(None) + + +@pass_context +async def delete_value(ctx: dict, options: dict) -> DeleteValueResult: + async with ctx[GraphContext.DB_ENGINE].acquire() as conn: + await actions.delete_value( + options["id"], + conn=conn, + changes=ctx[GraphContext.VALUES_CHANGES], + ) + + return DeleteValueResult(None) + + mutation_data_types = { "SaveFlagOperation": Record[{"type": String, "payload": Any}], + "SaveValueOperation": Record[{"type": String, "payload": Any}], } MUTATION_GRAPH = Graph( @@ -638,8 +1040,11 @@ async def delete_flag(ctx: dict, options: dict) -> DeleteFlagResult: SignInNode, SignOutNode, SaveFlagNode, + SaveValueNode, ResetFlagNode, + ResetValueNode, DeleteFlagNode, + DeleteValueNode, Root( [ Link( @@ -678,6 +1083,32 @@ async def delete_flag(ctx: dict, options: dict) -> DeleteFlagResult: options=[Option("id", String)], requires=None, ), + Link( + "saveValue", + TypeRef["SaveValue"], + save_value, + options=[ + Option( + "operations", + Sequence[TypeRef["SaveValueOperation"]], + ), + ], + requires=None, + ), + Link( + "resetValue", + TypeRef["ResetValue"], + reset_value, + options=[Option("id", String)], + requires=None, + ), + Link( + "deleteValue", + TypeRef["DeleteValue"], + delete_value, + options=[Option("id", String)], + requires=None, + ), ] ), ], diff --git a/featureflags/graph/types.py b/featureflags/graph/types.py index 467bcf8..d06dccc 100644 --- a/featureflags/graph/types.py +++ b/featureflags/graph/types.py @@ -11,6 +11,7 @@ class GraphContext(Enum): LDAP_SERVICE = "LDAP_SERVICE" DIRTY_PROJECTS = "DIRTY_PROJECTS" CHANGES = "CHANGES" + VALUES_CHANGES = "VALUES_CHANGES" CHECK_IDS = "CHECK_IDS" @@ -20,6 +21,11 @@ class Operation(Enum): ADD_CHECK = "add_check" ADD_CONDITION = "add_condition" DISABLE_CONDITION = "disable_condition" + DISABLE_VALUE = "disable_value" + ENABLE_VALUE = "enable_value" + ADD_VALUE_CONDITION = "add_value_condition" + DISABLE_VALUE_CONDITION = "disable_value_condition" + UPDATE_VALUE_VALUE_OVERRIDE = "update_value_value_override" class Action(Enum): @@ -31,9 +37,20 @@ class Action(Enum): DELETE_FLAG = 6 +class ValueAction(Enum): + ENABLE_VALUE = 1 + DISABLE_VALUE = 2 + ADD_CONDITION = 3 + DISABLE_CONDITION = 4 + RESET_VALUE = 5 + DELETE_VALUE = 6 + UPDATE_VALUE_VALUE_OVERRIDE = 7 + + class DirtyProjects: def __init__(self) -> None: self.by_flag: set[UUID] = set() + self.by_value: set[UUID] = set() self.by_variable: set[UUID] = set() @@ -50,6 +67,19 @@ def get_actions(self) -> list[tuple[UUID, list[Action]]]: return list(self._data.items()) +class ValuesChanges: + _data: dict[UUID, list[ValueAction]] + + def __init__(self) -> None: + self._data = defaultdict(list) + + def add(self, value_id: UUID, action: ValueAction) -> None: + self._data[value_id].append(action) + + def get_actions(self) -> list[tuple[UUID, list[ValueAction]]]: + return list(self._data.items()) + + @dataclass class LocalId: scope: str @@ -115,6 +145,37 @@ def __init__(self, op: dict): ] +@dataclass +class AddValueConditionOp: + @dataclass + class Check: + local_id: LocalId | None = None + id: str | None = None + + value_id: str + local_id: LocalId + checks: list[Check] + + def __init__(self, op: dict): + self.local_id = LocalId( + scope=op["local_id"]["scope"], + value=op["local_id"]["value"], + ) + self.value_id = op["value_id"] + self.checks = [ + self.Check( + local_id=LocalId( + scope=check["local_id"]["scope"], + value=check["local_id"]["value"], + ) + if "local_id" in check + else None, + id=check.get("id"), + ) + for check in op["checks"] + ] + + class AuthResult(NamedTuple): error: str | None = None @@ -129,3 +190,15 @@ class ResetFlagResult(NamedTuple): class DeleteFlagResult(NamedTuple): error: str | None = None + + +class SaveValueResult(NamedTuple): + errors: list[str] | None = None + + +class ResetValueResult(NamedTuple): + error: str | None = None + + +class DeleteValueResult(NamedTuple): + error: str | None = None diff --git a/featureflags/http/app.py b/featureflags/http/app.py index c42096e..76cbbc2 100644 --- a/featureflags/http/app.py +++ b/featureflags/http/app.py @@ -28,7 +28,7 @@ def create_app() -> FastAPI: configure_lifecycle(app, container) if config.sentry.enabled: - from featureflags.sentry import configure_sentry, SentryMode + from featureflags.sentry import SentryMode, configure_sentry configure_sentry(config.sentry, env_prefix="http", mode=SentryMode.HTTP) diff --git a/featureflags/http/db.py b/featureflags/http/db.py index 5028209..41eabe4 100644 --- a/featureflags/http/db.py +++ b/featureflags/http/db.py @@ -15,7 +15,7 @@ from featureflags.http.types import ( Variable as RequestVariable, ) -from featureflags.models import Flag, Project, Variable +from featureflags.models import Flag, Project, Value, Variable from featureflags.utils import EntityCache @@ -114,7 +114,12 @@ async def _select_flag(project: UUID, name: str, *, conn: SAConnection) -> UUID: return await result.scalar() -async def _insert_flag(project: UUID, name: str, *, conn: SAConnection) -> UUID: +async def _insert_flag( + project: UUID, + name: str, + *, + conn: SAConnection, +) -> UUID | None: result = await conn.execute( insert(Flag.__table__) .values({Flag.id: uuid4(), Flag.project: project, Flag.name: name}) @@ -144,6 +149,65 @@ async def _get_or_create_flag( return id_ +async def _select_value( + project: UUID, + name: str, + *, + conn: SAConnection, +) -> UUID | None: + result = await conn.execute( + select([Value.id]).where( + and_(Value.project == project, Value.name == name) + ) + ) + return await result.scalar() + + +async def _insert_value( + project: UUID, + name: str, + value_default: str, + *, + conn: SAConnection, +) -> UUID | None: + result = await conn.execute( + insert(Value.__table__) + .values( + { + Value.id: uuid4(), + Value.project: project, + Value.name: name, + Value.value_default: value_default, + Value.value_override: value_default, + } + ) + .on_conflict_do_nothing() + .returning(Value.id) + ) + return await result.scalar() + + +async def _get_or_create_value( + project: UUID, + value: str, + value_default: str, + *, + conn: SAConnection, + entity_cache: EntityCache, +) -> UUID: + assert project and value, (project, value) + id_ = entity_cache.value[project].get(value) + if id_ is None: # not in cache + id_ = await _select_value(project, value, conn=conn) + if id_ is None: # not in db + id_ = await _insert_value(project, value, value_default, conn=conn) + if id_ is None: # conflicting insert + id_ = await _select_value(project, value, conn=conn) + assert id_ is not None # must be in db + entity_cache.value[project][value] = id_ + return id_ + + async def prepare_flags_project( request: PreloadFlagsRequest, conn: SAConnection, @@ -168,3 +232,12 @@ async def prepare_flags_project( conn=conn, entity_cache=entity_cache, ) + for value in request.values: + value_name, value_value_default = value + await _get_or_create_value( + project, + value_name, + str(value_value_default), + conn=conn, + entity_cache=entity_cache, + ) diff --git a/featureflags/http/repositories/flags.py b/featureflags/http/repositories/flags.py index 314185a..409522a 100644 --- a/featureflags/http/repositories/flags.py +++ b/featureflags/http/repositories/flags.py @@ -12,13 +12,14 @@ PreloadFlagsResponse, SyncFlagsRequest, SyncFlagsResponse, + Value, ) from featureflags.models import Project from featureflags.services.auth import user_session from featureflags.utils import EntityCache, select_scalar -def load_flags_query(project: str) -> QueryNode: +def load_data_query(project: str) -> QueryNode: return build( [ Q.flags(project_name=project)[ @@ -43,6 +44,31 @@ def load_flags_query(project: str) -> QueryNode: ], ], ], + Q.values(project_name=project)[ + Q.id, + Q.name, + Q.enabled, + Q.overridden, + Q.value_default, + Q.value_override, + Q.conditions[ + Q.id, + Q.value_override, + Q.checks[ + Q.id, + Q.variable[ + Q.id, + Q.name, + Q.type, + ], + Q.operator, + Q.value_string, + Q.value_number, + Q.value_timestamp, + Q.value_set, + ], + ], + ], ] ) @@ -92,14 +118,16 @@ async def load(self, request: PreloadFlagsRequest) -> PreloadFlagsResponse: result = await exec_denormalize_graph( graph_engine=self._graph_engine, - query=load_flags_query(request.project), + query=load_data_query(request.project), db_engine=self._db_engine, session=user_session.get(), ) flags = [Flag(**flag) for flag in result["flags"]] + values = [Value(**value) for value in result["values"]] return PreloadFlagsResponse( flags=flags, + values=values, version=current_version, ) @@ -114,16 +142,19 @@ async def sync(self, request: SyncFlagsRequest) -> SyncFlagsResponse: if request.version != current_version: result = await exec_denormalize_graph( graph_engine=self._graph_engine, - query=load_flags_query(request.project), + query=load_data_query(request.project), db_engine=self._db_engine, session=user_session.get(), ) flags = [Flag(**flag) for flag in result["flags"]] + values = [Value(**value) for value in result["values"]] else: - # Don't load flags if version is the same. + # Don't load flags and values if version is the same. flags = [] + values = [] return SyncFlagsResponse( flags=flags, + values=values, version=current_version, ) diff --git a/featureflags/http/types.py b/featureflags/http/types.py index 76ecb91..8ff8a1e 100644 --- a/featureflags/http/types.py +++ b/featureflags/http/types.py @@ -52,6 +52,20 @@ class Flag(BaseModel): conditions: list[Condition] +class ValueCondition(BaseModel): + checks: list[Check] + value_override: str + + +class Value(BaseModel): + name: str + enabled: bool + overridden: bool + conditions: list[ValueCondition] + value_default: str + value_override: str + + class Variable(BaseModel): name: str type: VariableType @@ -62,10 +76,12 @@ class PreloadFlagsRequest(BaseModel): version: int variables: list[Variable] = Field(default_factory=list) flags: list[str] = Field(default_factory=list) + values: list[tuple[str, str | int]] = Field(default_factory=list) class PreloadFlagsResponse(BaseModel): flags: list[Flag] = Field(default_factory=list) + values: list[Value] = Field(default_factory=list) version: int @@ -73,8 +89,10 @@ class SyncFlagsRequest(BaseModel): project: str version: int flags: list[str] = Field(default_factory=list) + values: list[str] = Field(default_factory=list) class SyncFlagsResponse(BaseModel): flags: list[Flag] = Field(default_factory=list) + values: list[Value] = Field(default_factory=list) version: int diff --git a/featureflags/migrations/versions/1876f90b58e8_added_feature_values_tables.py b/featureflags/migrations/versions/1876f90b58e8_added_feature_values_tables.py new file mode 100644 index 0000000..3ca75d2 --- /dev/null +++ b/featureflags/migrations/versions/1876f90b58e8_added_feature_values_tables.py @@ -0,0 +1,107 @@ +import sqlalchemy as sa + +from alembic import op +from sqlalchemy.dialects import postgresql + + +revision = "1876f90b58e8" +down_revision = "8df4e7dd1897" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "value", + sa.Column("id", postgresql.UUID(as_uuid=True), nullable=False), + sa.Column("name", sa.String(), nullable=False), + sa.Column("enabled", sa.Boolean(), nullable=True), + sa.Column("value_default", sa.String(), nullable=False), + sa.Column("value_override", sa.String(), nullable=False), + sa.Column("project", postgresql.UUID(as_uuid=True), nullable=False), + sa.ForeignKeyConstraint( + ["project"], + ["project.id"], + ), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("project", "name"), + ) + op.create_index( + "value_project_name_idx", "value", ["project", "name"], unique=False + ) + op.create_table( + "value_changelog", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("timestamp", postgresql.TIMESTAMP(), nullable=False), + sa.Column("auth_user", postgresql.UUID(as_uuid=True), nullable=False), + sa.Column("value", postgresql.UUID(as_uuid=True), nullable=False), + sa.Column( + "actions", + postgresql.ARRAY( + sa.Enum( + "ENABLE_VALUE", + "DISABLE_VALUE", + "ADD_CONDITION", + "DISABLE_CONDITION", + "RESET_VALUE", + "DELETE_VALUE", + "UPDATE_VALUE_VALUE_OVERRIDE", + name="value_changelog_actions", + ) + ), + nullable=True, + ), + sa.ForeignKeyConstraint( + ["auth_user"], + ["auth_user.id"], + ), + sa.ForeignKeyConstraint(["value"], ["value.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "value_condition", + sa.Column("id", postgresql.UUID(as_uuid=True), nullable=False), + sa.Column("value", postgresql.UUID(as_uuid=True), nullable=False), + sa.Column("value_override", sa.String(), nullable=False), + sa.Column( + "checks", + postgresql.ARRAY(postgresql.UUID(as_uuid=True), as_tuple=True), + nullable=True, + ), + sa.ForeignKeyConstraint( + ["value"], + ["value.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.drop_table("stats") + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "stats", + sa.Column( + "flag", postgresql.UUID(), autoincrement=False, nullable=False + ), + sa.Column( + "interval", + postgresql.TIMESTAMP(), + autoincrement=False, + nullable=False, + ), + sa.Column( + "positive_count", sa.INTEGER(), autoincrement=False, nullable=True + ), + sa.Column( + "negative_count", sa.INTEGER(), autoincrement=False, nullable=True + ), + sa.PrimaryKeyConstraint("flag", "interval", name="stats_pkey"), + ) + op.drop_table("value_condition") + op.drop_table("value_changelog") + op.drop_index("value_project_name_idx", table_name="value") + op.drop_table("value") + # ### end Alembic commands ### diff --git a/featureflags/models.py b/featureflags/models.py index 6347ba1..3b8c5a1 100644 --- a/featureflags/models.py +++ b/featureflags/models.py @@ -14,7 +14,7 @@ from sqlalchemy.schema import Column, ForeignKey, MetaData from sqlalchemy.types import Boolean, Enum, String -from featureflags.graph.types import Action +from featureflags.graph.types import Action, ValueAction from featureflags.protobuf.graph_pb2 import Check as CheckProto from featureflags.protobuf.graph_pb2 import Variable as VariableProto from featureflags.utils import ArrayOfEnum @@ -183,3 +183,47 @@ class Changelog(Base): actions = Column( ArrayOfEnum(Enum(Action, name="changelog_actions"), as_tuple=True) ) + + +class Value(Base): + __tablename__ = "value" + + id = Column(UUID(as_uuid=True), primary_key=True) + name = Column(String, nullable=False) + enabled = Column(Boolean) + value_default = Column(String, nullable=False) + value_override = Column(String, nullable=False) + + project: UUID = Column(ForeignKey("project.id"), nullable=False) + + __table_args__ = ( + UniqueConstraint(project, name), + Index("value_project_name_idx", project, name), + ) + + +class ValueCondition(Base): + __tablename__ = "value_condition" + + id = Column(UUID(as_uuid=True), primary_key=True) + value: UUID = Column(ForeignKey("value.id"), nullable=False) + value_override = Column(String, nullable=False) + + checks = Column("checks", ARRAY(UUID(as_uuid=True), as_tuple=True)) + + +class ValueChangelog(Base): + __tablename__ = "value_changelog" + + id = Column(Integer, primary_key=True) + + timestamp = Column(TIMESTAMP, nullable=False) + auth_user: UUID = Column(ForeignKey("auth_user.id"), nullable=False) + value: UUID = Column( + ForeignKey("value.id", ondelete="CASCADE"), nullable=False + ) + actions = Column( + ArrayOfEnum( + Enum(ValueAction, name="value_changelog_actions"), as_tuple=True + ) + ) diff --git a/featureflags/rpc/app.py b/featureflags/rpc/app.py index 363260c..54d3c28 100644 --- a/featureflags/rpc/app.py +++ b/featureflags/rpc/app.py @@ -44,7 +44,7 @@ async def main() -> None: set_internal_user_session() if config.sentry.enabled: - from featureflags.sentry import configure_sentry, SentryMode + from featureflags.sentry import SentryMode, configure_sentry configure_sentry( config.sentry, env_prefix="rpc", mode=SentryMode.GRPC diff --git a/featureflags/services/ldap.py b/featureflags/services/ldap.py index 2ce3cf8..3497410 100644 --- a/featureflags/services/ldap.py +++ b/featureflags/services/ldap.py @@ -78,7 +78,7 @@ async def check_credentials( error_msg = "Invalid username or password" else: try: - error_msg = getattr(e, "message") + error_msg = e.message except AttributeError: error_msg = str(e) error_msg = f"Error: {error_msg}" diff --git a/featureflags/tests/conftest.py b/featureflags/tests/conftest.py index 75e481c..0c5c528 100644 --- a/featureflags/tests/conftest.py +++ b/featureflags/tests/conftest.py @@ -10,7 +10,7 @@ from sqlalchemy import text from featureflags.alembic import main as alembic_main -from featureflags.graph.types import Changes, DirtyProjects +from featureflags.graph.types import Changes, DirtyProjects, ValuesChanges from featureflags.models import metadata from featureflags.services.auth import TestSession, user_session from featureflags.services.ldap import BaseLDAP @@ -93,6 +93,11 @@ def changes() -> Changes: return Changes() +@pytest.fixture +def values_changes() -> ValuesChanges: + return ValuesChanges() + + @pytest.fixture def graph_engine(container: "Container") -> Engine: return container.graph_engine() diff --git a/featureflags/tests/state.py b/featureflags/tests/state.py index baf0e8f..f48469f 100644 --- a/featureflags/tests/state.py +++ b/featureflags/tests/state.py @@ -14,6 +14,9 @@ Flag, Operator, Project, + Value, + ValueChangelog, + ValueCondition, Variable, VariableType, ) @@ -193,3 +196,71 @@ async def mk_changelog_entry( "actions": actions, }, ) + + +async def mk_value( + db_engine, + *, + id=uuid4, + name=f.pystr, + enabled=None, + project=None, + value_default=f.pystr, + value_override=f.pystr, +): + project = project or await mk_project(db_engine) + return await _flush( + db_engine, + Value, + { + "id": _val(id), + "name": _val(name), + "enabled": _val(enabled), + "project": project.id, + "value_default": _val(value_default), + "value_override": _val(value_override), + }, + ) + + +async def mk_value_condition( + db_engine, + *, + id=uuid4, + value=None, + checks=None, + project=None, + value_override=f.pystr, +): + project = project or await mk_project(db_engine) + value = value or await mk_value(db_engine, project=project) + checks = checks or [ + (await mk_check(db_engine, variable_project=project)).id + ] + return await _flush( + db_engine, + ValueCondition, + { + "id": _val(id), + "value": value.id, + "checks": checks, + "value_override": _val(value_override), + }, + ) + + +async def mk_value_changelog_entry( + db_engine, *, timestamp=None, auth_user=None, value=None, actions=() +): + auth_user = auth_user or await mk_auth_user(db_engine) + value = value or await mk_value(db_engine) + return await _flush( + db_engine, + ValueChangelog, + { + "timestamp": timestamp or datetime.utcnow(), + "auth_user": auth_user.id, + "value": value.id, + "actions": actions, + }, + ) diff --git a/featureflags/tests/test_actions.py b/featureflags/tests/test_actions.py index c75e6a2..e698c7f 100644 --- a/featureflags/tests/test_actions.py +++ b/featureflags/tests/test_actions.py @@ -7,20 +7,29 @@ from featureflags.graph.actions import ( AddCheckOp, AddConditionOp, + AddValueConditionOp, Changes, LocalId, add_check, add_condition, + add_value_condition, disable_condition, disable_flag, + disable_value, + disable_value_condition, enable_flag, + enable_value, gen_id, postprocess, reset_flag, + reset_value, sign_in, sign_out, update_changelog, + update_value_changelog, + update_value_value_override, ) +from featureflags.graph.types import ValueAction, ValuesChanges from featureflags.models import ( Action, AuthSession, @@ -32,6 +41,9 @@ LocalIdMap, Operator, Project, + Value, + ValueChangelog, + ValueCondition, ) from featureflags.services import auth from featureflags.services.auth import ( @@ -48,6 +60,8 @@ mk_condition, mk_flag, mk_project, + mk_value, + mk_value_condition, mk_variable, ) from featureflags.utils import select_scalar @@ -67,6 +81,20 @@ async def check_flag(flag, *, conn): return await result.scalar() +async def check_value(value, *, conn): + result = await conn.execute( + select([Value.enabled]).where(Value.id == value) + ) + return await result.scalar() + + +async def check_value_override(value, *, conn): + result = await conn.execute( + select([Value.value_override]).where(Value.id == value) + ) + return await result.scalar() + + @pytest.mark.asyncio async def test_sign_in_new(conn): username = "user@host.com" @@ -498,6 +526,9 @@ async def test_postprocess_by_all(conn, db_engine, dirty_projects): variable = await mk_variable(db_engine, project=project) dirty_projects.by_variable.add(variable.id) + value = await mk_value(db_engine, project=project) + dirty_projects.by_value.add(value.id) + await postprocess(conn=conn, dirty=dirty_projects) assert await get_version(project.id, conn=conn) == version + 1 assert await get_version(project_dub.id, conn=conn) == version @@ -527,3 +558,306 @@ async def test_update_changelog(conn, db_engine): Action.DISABLE_CONDITION, Action.ADD_CONDITION, ) + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "action, before, after, action_type", + [ + (enable_value, None, True, ValueAction.ENABLE_VALUE), + (enable_value, False, True, ValueAction.ENABLE_VALUE), + (disable_value, None, False, ValueAction.DISABLE_VALUE), + (disable_value, True, False, ValueAction.DISABLE_VALUE), + ], +) +async def test_switching_value( + conn, + db_engine, + dirty_projects, + values_changes, + action, + before, + after, + action_type, +): + value = await mk_value(db_engine, enabled=before) + assert await check_value(value.id, conn=conn) == before + + await action( + value_id=value.id.hex, + conn=conn, + dirty=dirty_projects, + changes=values_changes, + ) + assert dirty_projects.by_value == {value.id} + assert values_changes.get_actions() == [(value.id, [action_type])] + assert await check_value(value.id, conn=conn) is after + + +@pytest.mark.asyncio +async def test_reset_value(conn, db_engine, dirty_projects, values_changes): + project = await mk_project(db_engine) + value_default = "blabla" + value_override = "notblabla" + value = await mk_value( + db_engine, + enabled=True, + project=project, + value_default=value_default, + value_override=value_override, + ) + condition = await mk_value_condition( + db_engine, + value=value, + project=project, + value_override="value_override", + ) + + assert await check_value(value.id, conn=conn) is True + assert ( + await ( + await conn.execute( + select([exists().where(ValueCondition.id == condition.id)]) + ) + ).scalar() + is True + ) + + await reset_value( + value_id=value.id.hex, + conn=conn, + dirty=dirty_projects, + changes=values_changes, + ) + assert dirty_projects.by_value == {value.id} + assert values_changes.get_actions() == [ + (value.id, [ValueAction.RESET_VALUE]) + ] + + assert await check_value_override(value.id, conn=conn) == value_default + assert ( + await ( + await conn.execute( + select([exists().where(ValueCondition.id == condition.id)]) + ) + ).scalar() + is False + ) + + +@pytest.mark.asyncio +async def test_add_value_condition( + conn, db_engine, dirty_projects, values_changes +): + project = await mk_project(db_engine) + value = await mk_value(db_engine, project=project) + check = await mk_check(db_engine, variable_project=project) + + local_id = LocalId(scope="arra", value="sowle") + + ids = await add_value_condition( + AddValueConditionOp( + { + "local_id": {"scope": "arra", "value": "sowle"}, + "value_id": value.id.hex, + "checks": [{"id": check.id.hex}], + } + ), + conn=conn, + ids={}, + value_override="override_value", + dirty=dirty_projects, + changes=values_changes, + ) + assert dirty_projects.by_value == {value.id} + assert values_changes.get_actions() == [ + (value.id, [ValueAction.ADD_CONDITION]) + ] + + id_ = ids[local_id] + + result = await conn.execute( + select( + [ + ValueCondition.value, + ValueCondition.checks, + ValueCondition.value_override, + ] + ).where(ValueCondition.id == id_) + ) + row = await result.first() + assert row.as_tuple() == (value.id, (check.id,), "override_value") + + +@pytest.mark.asyncio +async def test_add_value_condition_and_check( + db_engine, conn, dirty_projects, values_changes +): + project = await mk_project(db_engine) + value = await mk_value(db_engine, project=project) + check1 = await mk_check(db_engine, variable_project=project) + check2 = await mk_check(db_engine, variable_project=project) + + check1_id = LocalId(scope="peggy", value="caseful") + + ids = {check1_id: check1.id} + + local_id = LocalId(scope="focal", value="klutzes") + + op = AddValueConditionOp( + { + "local_id": {"scope": local_id.scope, "value": local_id.value}, + "value_id": value.id.hex, + "checks": [ + { + "local_id": { + "scope": check1_id.scope, + "value": check1_id.value, + }, + }, + {"id": check2.id.hex}, + ], + } + ) + + ids.update( + await add_value_condition( + op, + conn=conn, + ids=ids, + dirty=dirty_projects, + changes=values_changes, + value_override="override_value", + ) + ) + assert dirty_projects.by_value == {value.id} + assert values_changes.get_actions() == [ + (value.id, [ValueAction.ADD_CONDITION]) + ] + + id_ = ids[local_id] + + result = await conn.execute( + select( + [ + ValueCondition.value, + ValueCondition.checks, + ValueCondition.value_override, + ] + ).where(ValueCondition.id == id_) + ) + row = await result.first() + assert row.as_tuple() == ( + value.id, + (check1.id, check2.id), + "override_value", + ) + + +@pytest.mark.asyncio +async def test_disable_value_condition( + conn, db_engine, dirty_projects, values_changes +): + condition = await mk_value_condition(db_engine) + + async def check_condition(): + result = await conn.execute( + select([ValueCondition.id]).where(ValueCondition.id == condition.id) + ) + row = await result.first() + return row and row[0] + + assert (await check_condition()) == condition.id + + await disable_value_condition( + condition.id.hex, + conn=conn, + dirty=dirty_projects, + changes=values_changes, + ) + assert dirty_projects.by_value == {condition.value} + assert values_changes.get_actions() == [ + (condition.value, [ValueAction.DISABLE_CONDITION]) + ] + + assert (await check_condition()) is None + + await disable_value_condition( + condition.id.hex, + conn=conn, + dirty=dirty_projects, + changes=values_changes, + ) + # should be the same + assert values_changes.get_actions() == [ + (condition.value, [ValueAction.DISABLE_CONDITION]) + ] + + assert (await check_condition()) is None + + +@pytest.mark.asyncio +async def test_postprocess_by_value(conn, db_engine, dirty_projects): + version = f.pyint() + project = await mk_project(db_engine, version=version) + value = await mk_value(db_engine, project=project) + dirty_projects.by_value.add(value.id) + await postprocess(conn=conn, dirty=dirty_projects) + assert await get_version(project.id, conn=conn) == version + 1 + + +@pytest.mark.asyncio +async def test_update_value_changelog(conn, db_engine): + value = await mk_value(db_engine) + user = await mk_auth_user(db_engine) + session = auth.TestSession(user.id) + + changes = ValuesChanges() + changes.add(value.id, ValueAction.ENABLE_VALUE) + changes.add(value.id, ValueAction.ADD_CONDITION) + changes.add(value.id, ValueAction.DISABLE_CONDITION) + changes.add(value.id, ValueAction.ADD_CONDITION) + + await update_value_changelog(session=session, conn=conn, changes=changes) + + actions = await select_scalar( + conn, + ( + select([ValueChangelog.actions]).where( + ValueChangelog.value == value.id + ) + ), + ) + assert actions == ( + ValueAction.ENABLE_VALUE, + ValueAction.ADD_CONDITION, + ValueAction.DISABLE_CONDITION, + ValueAction.ADD_CONDITION, + ) + + +@pytest.mark.asyncio +async def test_update_value_value_override( + conn, db_engine, dirty_projects, values_changes +): + project = await mk_project(db_engine) + value_default = "blabla" + value_override = "notblabla" + + value = await mk_value( + db_engine, + enabled=True, + project=project, + value_default=value_default, + value_override=value_default, + ) + + await update_value_value_override( + value.id.hex, + value_override=value_override, + conn=conn, + dirty=dirty_projects, + changes=values_changes, + ) + + assert await check_value_override(value.id, conn=conn) == value_override diff --git a/featureflags/tests/test_graph.py b/featureflags/tests/test_graph.py index 2fd0b45..c07018b 100644 --- a/featureflags/tests/test_graph.py +++ b/featureflags/tests/test_graph.py @@ -8,7 +8,7 @@ from featureflags.graph.graph import GRAPH, exec_graph from featureflags.graph.proto_adapter import populate_result_proto -from featureflags.graph.types import Action +from featureflags.graph.types import Action, ValueAction from featureflags.graph.utils import is_valid_uuid from featureflags.protobuf import graph_pb2 from featureflags.services.auth import ( @@ -26,6 +26,9 @@ mk_condition, mk_flag, mk_project, + mk_value, + mk_value_changelog_entry, + mk_value_condition, mk_variable, ) @@ -392,3 +395,244 @@ async def test_changes_by_project_ids(db_engine, graph_engine, test_session): }, ], } + + +@pytest.mark.asyncio +async def test_root_value_invalid(db_engine, graph_engine, test_session): + query = build( + [ + Q.value(id=str(uuid4()))[Q.id,], + ] + ) + result = await exec_graph(graph_engine, query, db_engine, test_session) + assert denormalize(GRAPH, result) == {"value": None} + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "enabled, overridden", + [ + (None, False), + (True, True), + (False, True), + ], +) +async def test_values( + enabled, overridden, db_engine, graph_engine, test_session +): + value_default = "test_values_value_default" + value_override = "test_values_value_override" + value_condition_override = "test_values_value_condition_override" + + project = await mk_project(db_engine) + variable = await mk_variable(db_engine, project=project) + value = await mk_value( + db_engine, + enabled=enabled, + project=project, + value_default=value_default, + value_override=value_override, + ) + check = await mk_check(db_engine, variable=variable) + condition = await mk_value_condition( + db_engine, + value=value, + checks=[check.id], + value_override=value_condition_override, + ) + + # generate some other value in other project + await mk_value(db_engine) + + query = build( + [ + Q.values(project_name=project.name)[ + Q.id, + Q.name, + Q.value_default, + Q.value_override, + Q.project[ + Q.id, + Q.name, + Q.variables[ + Q.id, + Q.name, + Q.type, + ], + ], + Q.enabled, + Q.conditions[ + Q.id, + Q.value_override, + Q.checks[ + Q.id, + Q.variable[ + Q.id, + Q.name, + Q.type, + ], + Q.operator, + Q.value_string, + Q.value_number, + Q.value_timestamp, + Q.value_set, + ], + ], + Q.overridden, + ], + ] + ) + result = await exec_graph(graph_engine, query, db_engine, test_session) + assert denormalize(GRAPH, result) == { + "values": [ + { + "id": value.id, + "name": value.name, + "value_default": value.value_default, + "value_override": value.value_override, + "project": { + "id": project.id, + "name": project.name, + "variables": [ + { + "id": variable.id, + "name": variable.name, + "type": variable.type, + }, + ], + }, + "enabled": value.enabled or False, + "conditions": [ + { + "id": condition.id, + "value_override": condition.value_override, + "checks": [ + { + "id": check.id, + "variable": { + "id": variable.id, + "name": variable.name, + "type": variable.type, + }, + "operator": check.operator, + "value_string": check.value_string, + "value_number": None, + "value_timestamp": None, + "value_set": None, + }, + ], + }, + ], + "overridden": overridden, + }, + ], + } + + +@pytest.mark.asyncio +async def test_values_by_ids(db_engine, graph_engine, test_session): + value = await mk_value(db_engine) + # generate some other value + await mk_value(db_engine) + + query = build( + [ + Q.values_by_ids(ids=["invalid", value.id.hex, "invalid"])[ + Q.id, + Q.name, + ], + ] + ) + result = await exec_graph(graph_engine, query, db_engine, test_session) + assert denormalize(GRAPH, result) == { + "values_by_ids": [ + { + "id": value.id, + "name": value.name, + }, + ], + } + + +@pytest.mark.asyncio +async def test_value_changes(db_engine, graph_engine, test_session): + value = await mk_value(db_engine) + auth_user = await mk_auth_user(db_engine) + entry = await mk_value_changelog_entry( + db_engine, value=value, auth_user=auth_user + ) + query = build( + [ + Q.valueChanges[ + Q.id, + Q.timestamp, + Q.actions, + Q.value[Q.name], + Q.user[Q.username], + ], + ] + ) + result = await exec_graph(graph_engine, query, db_engine, test_session) + plain_result = denormalize(GRAPH, result) + assert plain_result["valueChanges"][0] == { # 0 == latest + "id": entry.id, + "timestamp": entry.timestamp, + "actions": (), + "value": {"name": value.name}, + "user": {"username": auth_user.username}, + } + + +@pytest.mark.asyncio +async def test_value_changes_by_project_ids( + db_engine, graph_engine, test_session +): + value = await mk_value(db_engine) + auth_user = await mk_auth_user(db_engine) + entry = await mk_value_changelog_entry( + db_engine, + value=value, + auth_user=auth_user, + actions=[ValueAction.RESET_VALUE], + ) + q1 = build( + [ + Q.valueChanges(project_ids=[])[ + Q.id, + Q.timestamp, + Q.actions, + Q.value[Q.name], + Q.user[Q.username], + ], + ] + ) + r1 = await exec_graph(graph_engine, q1, db_engine, test_session) + assert denormalize(GRAPH, r1) == {"valueChanges": []} + + q2 = build( + [ + Q.valueChanges(project_ids=[value.project])[ + Q.id, + Q.timestamp, + Q.actions, + Q.value[Q.name], + Q.user[Q.username], + ], + ] + ) + r2 = await exec_graph(graph_engine, q2, db_engine, test_session) + assert denormalize(GRAPH, r2) == { + "valueChanges": [ + { + "id": entry.id, + "timestamp": entry.timestamp, + "actions": (ValueAction.RESET_VALUE,), + "value": { + "name": value.name, + }, + "user": { + "username": auth_user.username, + }, + }, + ], + } diff --git a/featureflags/tests/test_web.py b/featureflags/tests/test_web.py index dfa8ebd..2e34a3c 100644 --- a/featureflags/tests/test_web.py +++ b/featureflags/tests/test_web.py @@ -1,14 +1,12 @@ import pytest -from hiku.endpoint.graphql import ( - AsyncBatchGraphQLEndpoint, -) +from hiku.endpoint.graphql import AsyncBatchGraphQLEndpoint from sqlalchemy import select from featureflags.graph import graph from featureflags.graph.context import init_graph_context -from featureflags.models import Flag +from featureflags.models import Flag, Value from featureflags.services import auth -from featureflags.tests.state import mk_auth_user, mk_flag +from featureflags.tests.state import mk_auth_user, mk_flag, mk_value async def check_flag(flag, conn): @@ -21,6 +19,18 @@ async def get_flag(flag, conn): return await result.scalar() +async def check_value(value, conn): + result = await conn.execute( + select([Value.value_override]).where(Value.id == value) + ) + return await result.scalar() + + +async def get_value(value, conn): + result = await conn.execute(select([Value.id]).where(Value.id == value)) + return await result.scalar() + + @pytest.mark.asyncio @pytest.mark.parametrize("authenticated", [True, False]) async def test_reset_flag_graph( @@ -99,3 +109,99 @@ async def test_delete_flag_graph( assert res["data"]["deleteFlag"]["error"] is None assert await get_flag(flag.id, conn) is None + + +@pytest.mark.asyncio +@pytest.mark.parametrize("authenticated", [True, False]) +async def test_reset_value_graph( + authenticated, + db_engine, + conn, + graph_engine, + ldap, +): + value_default = "reseted" + value_override = "valued" + + value = await mk_value( + db_engine, + enabled=True, + value_default=value_default, + value_override=value_override, + ) + + query = { + "query": """ + mutation ResetValue($id: String!) { + resetValue(id: $id) { error } + } + """, + "variables": {"id": str(value.id)}, + } + graphql_endpoint = AsyncBatchGraphQLEndpoint( + engine=graph_engine, + query_graph=graph.GRAPH, + mutation_graph=graph.MUTATION_GRAPH, + ) + + async def make_call(*, user=None): + session = auth.TestSession(user=user) + ctx = init_graph_context( + session=session, + engine=db_engine, + ldap=ldap, + ) + + return await graphql_endpoint.dispatch(query, ctx) + + if authenticated: + user = await mk_auth_user(db_engine) + await make_call(user=user.id) + assert await check_value(value.id, conn) == value_default + else: + with pytest.raises(AssertionError): + await make_call(user=None) + + +@pytest.mark.asyncio +async def test_delete_value_graph( + db_engine, + conn, + graph_engine, + ldap, +): + value_default = "enabled" + value_override = "deleted" + + value = await mk_value( + db_engine, + enabled=True, + value_default=value_default, + value_override=value_override, + ) + + query = { + "query": """ + mutation DeleteValue($id: String!) { + deleteValue(id: $id) { error } + } + """, + "variables": {"id": str(value.id)}, + } + user = await mk_auth_user(db_engine) + + graphql_endpoint = AsyncBatchGraphQLEndpoint( + engine=graph_engine, + query_graph=graph.GRAPH, + mutation_graph=graph.MUTATION_GRAPH, + ) + ctx = init_graph_context( + session=auth.TestSession(user.id), + engine=db_engine, + ldap=ldap, + ) + + res = await graphql_endpoint.dispatch(query, ctx) + assert res["data"]["deleteValue"]["error"] is None + + assert await get_value(value.id, conn) is None diff --git a/featureflags/utils.py b/featureflags/utils.py index f3b0e62..c79fc1f 100644 --- a/featureflags/utils.py +++ b/featureflags/utils.py @@ -42,6 +42,7 @@ class EntityCache: def __init__(self) -> None: self.project: dict[str, UUID] = {} self.flag: defaultdict[UUID, dict[str, UUID]] = defaultdict(dict) + self.value: defaultdict[UUID, dict[str, UUID]] = defaultdict(dict) self.variable: defaultdict[UUID, dict[str, UUID]] = defaultdict(dict) @@ -56,6 +57,17 @@ def __init__(self) -> None: super().__init__(lambda: defaultdict(lambda: [0, 0])) +class ValueAggStats(defaultdict): + """ + Used to collect values statistics in aggregated state + + acc[interval][value] -> [positive_count, negative_count] + """ + + def __init__(self) -> None: + super().__init__(lambda: defaultdict(lambda: [0, 0])) + + async def select_scalar(conn: SAConnection, stmt: Any) -> Any: result = await conn.execute(stmt) return await result.scalar() diff --git a/featureflags/web/app.py b/featureflags/web/app.py index 21bf991..86cc0a9 100644 --- a/featureflags/web/app.py +++ b/featureflags/web/app.py @@ -38,7 +38,7 @@ def create_app() -> FastAPI: configure_lifecycle(app, container) if config.sentry.enabled: - from featureflags.sentry import configure_sentry, SentryMode + from featureflags.sentry import SentryMode, configure_sentry configure_sentry(config.sentry, env_prefix="web", mode=SentryMode.HTTP) diff --git a/pyproject.toml b/pyproject.toml index 11b4c3d..48521fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -144,6 +144,7 @@ ignore = [ "DTZ006", "A003", "B008", + "PLR0913", ] exclude = [ ".bzr", diff --git a/ui/package-lock.json b/ui/package-lock.json index 7e44d21..4ea8ac3 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -127,12 +127,14 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.16.7" + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -178,19 +180,36 @@ } }, "node_modules/@babel/generator": { - "version": "7.17.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.10.tgz", - "integrity": "sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg==", + "version": "7.25.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.5.tgz", + "integrity": "sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/types": "^7.17.10", - "@jridgewell/gen-mapping": "^0.1.0", + "@babel/types": "^7.25.4", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/helper-annotate-as-pure": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", @@ -233,31 +252,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-function-name": { - "version": "7.17.9", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz", - "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==", - "dev": true, - "dependencies": { - "@babel/template": "^7.16.7", - "@babel/types": "^7.17.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-module-imports": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", @@ -322,11 +316,22 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -355,24 +360,30 @@ } }, "node_modules/@babel/highlight": { - "version": "7.17.9", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.9.tgz", - "integrity": "sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.17.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.10.tgz", - "integrity": "sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.4.tgz", + "integrity": "sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA==", "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.4" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -471,34 +482,33 @@ } }, "node_modules/@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.17.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.10.tgz", - "integrity": "sha512-VmbrTHQteIdUUQNTb+zE12SHS/xQVIShmBPhlNP12hD5poF2pbITW1Z4172d03HegaQWhLffdkRJYtAzp0AGcw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.10", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.17.9", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.17.10", - "@babel/types": "^7.17.10", - "debug": "^4.1.0", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.4.tgz", + "integrity": "sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.4", + "@babel/parser": "^7.25.4", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.4", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -506,12 +516,14 @@ } }, "node_modules/@babel/types": { - "version": "7.17.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.10.tgz", - "integrity": "sha512-9O26jG0mBYfGkUYCYZRnBwbVLd1UZOICEr2Em6InB6jVfsAv1GKgwXHmrSg+WFWDmeKTA6vyTZiN8tCSM5Oo3A==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.4.tgz", + "integrity": "sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -548,37 +560,69 @@ } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", - "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz", - "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.13", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", - "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", - "dev": true + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.10.tgz", - "integrity": "sha512-Q0YbBd6OTsXm8Y21+YUSDXupHnodNC2M4O18jtd3iwJ3+vMZNdKGols0a9G6JOK0dcJ3IdUUHoh908ZI6qhk8Q==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@rollup/pluginutils": { @@ -594,48 +638,32 @@ "node": ">= 8.0.0" } }, - "node_modules/@types/eslint": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.2.tgz", - "integrity": "sha512-Z1nseZON+GEnFjJc04sv4NSALGjhFwy6K0HXt7qsn5ArfAKtb63dXNJHf+1YW6IpOIYRBGUbu3GwJdj8DGnCjA==", - "dev": true, - "peer": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz", - "integrity": "sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==", - "dev": true, - "peer": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/@types/node": { - "version": "17.0.31", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.31.tgz", - "integrity": "sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q==", + "version": "22.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.0.tgz", + "integrity": "sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==", "dev": true, - "peer": true + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~6.19.2" + } }, "node_modules/@types/prop-types": { "version": "15.7.5", @@ -689,163 +717,178 @@ } }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "dev": true, + "license": "Apache-2.0", "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/ast": "1.12.1", "@xtuc/long": "4.2.2" } }, @@ -887,6 +930,7 @@ "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", "dev": true, + "license": "BSD-3-Clause", "peer": true }, "node_modules/@xtuc/long": { @@ -894,13 +938,15 @@ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true, + "license": "Apache-2.0", "peer": true }, "node_modules/acorn": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", - "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, + "license": "MIT", "peer": true, "bin": { "acorn": "bin/acorn" @@ -909,11 +955,12 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "dev": true, + "license": "MIT", "peer": true, "peerDependencies": { "acorn": "^8" @@ -924,6 +971,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", @@ -941,6 +989,7 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true, + "license": "MIT", "peer": true, "peerDependencies": { "ajv": "^6.9.1" @@ -951,6 +1000,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, @@ -1028,9 +1078,9 @@ "integrity": "sha512-p4DO/JXwjs8klJyJL8Q2oM4ks5fUTze/h5k10oPPKMiLe1fj3G1QMzPHNmN1Py4ycOk7WlO2DcGXv1qiESJCZA==" }, "node_modules/browserslist": { - "version": "4.20.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz", - "integrity": "sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "dev": true, "funding": [ { @@ -1040,14 +1090,18 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001332", - "electron-to-chromium": "^1.4.118", - "escalade": "^3.1.1", - "node-releases": "^2.0.3", - "picocolors": "^1.0.0" + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" @@ -1061,12 +1115,13 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/caniuse-lite": { - "version": "1.0.30001338", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001338.tgz", - "integrity": "sha512-1gLHWyfVoRDsHieO+CaeYe7jSo/MT7D7lhaXUiwwbuR5BwQxORs0f1tAwUSQr3YbxRXJvxHM/PA5FfPQRnsPeQ==", + "version": "1.0.30001653", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001653.tgz", + "integrity": "sha512-XGWQVB8wFQ2+9NZwZ10GxTYC5hk0Fa+q8cSkr0tgvMhYhMHP/QC+WTgrePMDBWiWc/pV+1ik82Al20XOK25Gcw==", "dev": true, "funding": [ { @@ -1076,14 +1131,20 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -1113,6 +1174,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "1.1.3" } @@ -1120,14 +1182,16 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/compute-scroll-into-view": { @@ -1220,16 +1284,18 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.137", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.137.tgz", - "integrity": "sha512-0Rcpald12O11BUogJagX3HsCN3FE83DSqWjgXoHo5a72KUKMSfI39XBgJpgNNxS9fuGzytaFjE06kZkiVFy2qA==", - "dev": true + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz", + "integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==", + "dev": true, + "license": "ISC" }, "node_modules/enhanced-resolve": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz", - "integrity": "sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -1253,10 +1319,11 @@ } }, "node_modules/es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/esbuild": { @@ -1615,10 +1682,11 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -1626,8 +1694,9 @@ "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.0" } @@ -1700,6 +1769,7 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/fast-json-stable-stringify": { @@ -1707,6 +1777,7 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/fsevents": { @@ -1748,6 +1819,7 @@ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true, + "license": "BSD-2-Clause", "peer": true }, "node_modules/globals": { @@ -1760,15 +1832,17 @@ } }, "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" }, "node_modules/graphql": { - "version": "16.4.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.4.0.tgz", - "integrity": "sha512-tYDNcRvKCcfHREZYje3v33NSrSD/ZpbWWdPtBtUUuXx9NCo/2QDxYzNqCnMvfsrnbwRpEHMovVrPu/ERoLrIRg==", + "version": "16.9.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz", + "integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==", + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -1802,8 +1876,9 @@ "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -1873,6 +1948,7 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@types/node": "*", @@ -1888,6 +1964,7 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=8" @@ -1898,6 +1975,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "has-flag": "^4.0.0" @@ -1926,11 +2004,12 @@ "node": ">=4" } }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/json-schema-traverse": { @@ -1938,6 +2017,7 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/json2mq": { @@ -1949,10 +2029,11 @@ } }, "node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, + "license": "MIT", "bin": { "json5": "lib/cli.js" }, @@ -2030,13 +2111,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "node_modules/lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true, - "peer": true - }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -2072,10 +2146,11 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, + "license": "ISC", "optional": true, "bin": { "semver": "bin/semver" @@ -2091,6 +2166,7 @@ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/mime": { @@ -2130,9 +2206,10 @@ } }, "node_modules/moment": { - "version": "2.29.3", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz", - "integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==", + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", "engines": { "node": "*" } @@ -2144,10 +2221,17 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -2201,10 +2285,11 @@ } }, "node_modules/node-releases": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.4.tgz", - "integrity": "sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==", - "dev": true + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true, + "license": "MIT" }, "node_modules/object-assign": { "version": "4.1.1", @@ -2259,10 +2344,11 @@ "dev": true }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -2287,9 +2373,9 @@ } }, "node_modules/postcss": { - "version": "8.4.13", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.13.tgz", - "integrity": "sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==", + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", "dev": true, "funding": [ { @@ -2299,12 +2385,17 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "nanoid": "^3.3.3", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -2328,10 +2419,11 @@ "optional": true }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=6" @@ -2342,6 +2434,7 @@ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "safe-buffer": "^5.1.0" @@ -3062,10 +3155,11 @@ } }, "node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@types/json-schema": "^7.0.8", @@ -3089,19 +3183,21 @@ } }, "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, + "license": "BSD-3-Clause", "peer": true, "dependencies": { "randombytes": "^2.1.0" @@ -3122,10 +3218,11 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -3135,6 +3232,7 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "buffer-from": "^1.0.0", @@ -3151,6 +3249,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -3183,21 +3282,23 @@ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=6" } }, "node_modules/terser": { - "version": "5.13.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.13.1.tgz", - "integrity": "sha512-hn4WKOfwnwbYfe48NgrQjqNOH9jzLqRcIfbYytOXCOv46LBfWr9bDS17MQqOi+BWGD0sJK3Sj5NC/gJjiojaoA==", + "version": "5.31.6", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz", + "integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==", "dev": true, + "license": "BSD-2-Clause", "peer": true, "dependencies": { - "acorn": "^8.5.0", + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", "commander": "^2.20.0", - "source-map": "~0.8.0-beta.0", "source-map-support": "~0.5.20" }, "bin": { @@ -3208,17 +3309,18 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz", - "integrity": "sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { + "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1", - "terser": "^5.7.2" + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" }, "engines": { "node": ">= 10.13.0" @@ -3242,19 +3344,6 @@ } } }, - "node_modules/terser/node_modules/source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "dev": true, - "peer": true, - "dependencies": { - "whatwg-url": "^7.0.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -3269,16 +3358,6 @@ "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=" }, - "node_modules/tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "dev": true, - "peer": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/ts-invariant": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.10.2.tgz", @@ -3295,11 +3374,51 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "peer": true, "dependencies": { "punycode": "^2.1.0" @@ -3322,15 +3441,16 @@ } }, "node_modules/vite": { - "version": "2.9.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.8.tgz", - "integrity": "sha512-zsBGwn5UT3YS0NLSJ7hnR54+vUKfgzMUh/Z9CxF1YKEBVIe213+63jrFLmZphgGI5zXwQCSmqIdbPuE8NJywPw==", + "version": "2.9.18", + "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.18.tgz", + "integrity": "sha512-sAOqI5wNM9QvSEE70W3UGMdT8cyEn0+PmJMTFvTB8wB0YbYUWw3gUbY62AOyrXosGieF2htmeLATvNxpv/zNyQ==", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "^0.14.27", "postcss": "^8.4.13", "resolve": "^1.22.0", - "rollup": "^2.59.0" + "rollup": ">=2.59.0 <2.78.0" }, "bin": { "vite": "bin/vite.js" @@ -3447,10 +3567,11 @@ } }, "node_modules/watchpack": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", - "integrity": "sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", @@ -3460,43 +3581,36 @@ "node": ">=10.13.0" } }, - "node_modules/webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true, - "peer": true - }, "node_modules/webpack": { - "version": "5.72.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.72.0.tgz", - "integrity": "sha512-qmSmbspI0Qo5ld49htys8GY9XhS9CGqFoHTsOVAnjBdg0Zn79y135R+k4IR4rKK6+eKaabMhJwiVB7xw0SJu5w==", + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.4.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.7.1", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.9.2", - "es-module-lexer": "^0.9.0", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-better-errors": "^1.0.2", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", + "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.3.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": { @@ -3525,18 +3639,6 @@ "node": ">=10.13.0" } }, - "node_modules/whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "peer": true, - "dependencies": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, "node_modules/zen-observable": { "version": "0.8.15", "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", @@ -3620,12 +3722,13 @@ } }, "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, "requires": { - "@babel/highlight": "^7.16.7" + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" } }, "@babel/compat-data": { @@ -3658,14 +3761,28 @@ } }, "@babel/generator": { - "version": "7.17.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.10.tgz", - "integrity": "sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg==", + "version": "7.25.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.5.tgz", + "integrity": "sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w==", "dev": true, "requires": { - "@babel/types": "^7.17.10", - "@jridgewell/gen-mapping": "^0.1.0", + "@babel/types": "^7.25.4", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + } + } } }, "@babel/helper-annotate-as-pure": { @@ -3698,25 +3815,6 @@ "@babel/types": "^7.16.7" } }, - "@babel/helper-function-name": { - "version": "7.17.9", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz", - "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==", - "dev": true, - "requires": { - "@babel/template": "^7.16.7", - "@babel/types": "^7.17.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, "@babel/helper-module-imports": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", @@ -3766,10 +3864,16 @@ "@babel/types": "^7.16.7" } }, + "@babel/helper-string-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "dev": true + }, "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "dev": true }, "@babel/helper-validator-option": { @@ -3790,21 +3894,25 @@ } }, "@babel/highlight": { - "version": "7.17.9", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.9.tgz", - "integrity": "sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" } }, "@babel/parser": { - "version": "7.17.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.10.tgz", - "integrity": "sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ==", - "dev": true + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.4.tgz", + "integrity": "sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA==", + "dev": true, + "requires": { + "@babel/types": "^7.25.4" + } }, "@babel/plugin-syntax-jsx": { "version": "7.16.7", @@ -3864,41 +3972,39 @@ } }, "@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "dev": true, "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" } }, "@babel/traverse": { - "version": "7.17.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.10.tgz", - "integrity": "sha512-VmbrTHQteIdUUQNTb+zE12SHS/xQVIShmBPhlNP12hD5poF2pbITW1Z4172d03HegaQWhLffdkRJYtAzp0AGcw==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.4.tgz", + "integrity": "sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg==", "dev": true, "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.10", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.17.9", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.17.10", - "@babel/types": "^7.17.10", - "debug": "^4.1.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.4", + "@babel/parser": "^7.25.4", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.4", + "debug": "^4.3.1", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.17.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.10.tgz", - "integrity": "sha512-9O26jG0mBYfGkUYCYZRnBwbVLd1UZOICEr2Em6InB6jVfsAv1GKgwXHmrSg+WFWDmeKTA6vyTZiN8tCSM5Oo3A==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.4.tgz", + "integrity": "sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.16.7", + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" } }, @@ -3924,31 +4030,56 @@ } }, "@jridgewell/resolve-uri": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", - "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true }, "@jridgewell/set-array": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz", - "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true }, + "@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "peer": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "peer": true, + "requires": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + } + } + } + }, "@jridgewell/sourcemap-codec": { - "version": "1.4.13", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", - "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "dev": true }, "@jridgewell/trace-mapping": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.10.tgz", - "integrity": "sha512-Q0YbBd6OTsXm8Y21+YUSDXupHnodNC2M4O18jtd3iwJ3+vMZNdKGols0a9G6JOK0dcJ3IdUUHoh908ZI6qhk8Q==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "@rollup/pluginutils": { @@ -3961,48 +4092,29 @@ "picomatch": "^2.2.2" } }, - "@types/eslint": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.2.tgz", - "integrity": "sha512-Z1nseZON+GEnFjJc04sv4NSALGjhFwy6K0HXt7qsn5ArfAKtb63dXNJHf+1YW6IpOIYRBGUbu3GwJdj8DGnCjA==", - "dev": true, - "peer": true, - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "@types/eslint-scope": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz", - "integrity": "sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==", - "dev": true, - "peer": true, - "requires": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true, "peer": true }, "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, "peer": true }, "@types/node": { - "version": "17.0.31", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.31.tgz", - "integrity": "sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q==", + "version": "22.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.0.tgz", + "integrity": "sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==", "dev": true, - "peer": true + "peer": true, + "requires": { + "undici-types": "~6.19.2" + } }, "@types/prop-types": { "version": "15.7.5", @@ -4053,73 +4165,73 @@ } }, "@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "dev": true, "peer": true, "requires": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" } }, "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", "dev": true, "peer": true }, "@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", "dev": true, "peer": true }, "@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", "dev": true, "peer": true }, "@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "dev": true, "peer": true, "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", "@xtuc/long": "4.2.2" } }, "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", "dev": true, "peer": true }, "@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", "dev": true, "peer": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" } }, "@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "dev": true, "peer": true, "requires": { @@ -4127,9 +4239,9 @@ } }, "@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "dev": true, "peer": true, "requires": { @@ -4137,79 +4249,79 @@ } }, "@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", "dev": true, "peer": true }, "@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", "dev": true, "peer": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" } }, "@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", "dev": true, "peer": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", "dev": true, "peer": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" } }, "@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", "dev": true, "peer": true, "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, "@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", "dev": true, "peer": true, "requires": { - "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/ast": "1.12.1", "@xtuc/long": "4.2.2" } }, @@ -4252,16 +4364,16 @@ "peer": true }, "acorn": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", - "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, "peer": true }, - "acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "dev": true, "peer": true, "requires": {} @@ -4358,16 +4470,15 @@ "integrity": "sha512-p4DO/JXwjs8klJyJL8Q2oM4ks5fUTze/h5k10oPPKMiLe1fj3G1QMzPHNmN1Py4ycOk7WlO2DcGXv1qiESJCZA==" }, "browserslist": { - "version": "4.20.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz", - "integrity": "sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001332", - "electron-to-chromium": "^1.4.118", - "escalade": "^3.1.1", - "node-releases": "^2.0.3", - "picocolors": "^1.0.0" + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" } }, "buffer-from": { @@ -4378,9 +4489,9 @@ "peer": true }, "caniuse-lite": { - "version": "1.0.30001338", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001338.tgz", - "integrity": "sha512-1gLHWyfVoRDsHieO+CaeYe7jSo/MT7D7lhaXUiwwbuR5BwQxORs0f1tAwUSQr3YbxRXJvxHM/PA5FfPQRnsPeQ==", + "version": "1.0.30001653", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001653.tgz", + "integrity": "sha512-XGWQVB8wFQ2+9NZwZ10GxTYC5hk0Fa+q8cSkr0tgvMhYhMHP/QC+WTgrePMDBWiWc/pV+1ik82Al20XOK25Gcw==", "dev": true }, "chalk": { @@ -4418,7 +4529,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, "commander": { @@ -4500,15 +4611,15 @@ } }, "electron-to-chromium": { - "version": "1.4.137", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.137.tgz", - "integrity": "sha512-0Rcpald12O11BUogJagX3HsCN3FE83DSqWjgXoHo5a72KUKMSfI39XBgJpgNNxS9fuGzytaFjE06kZkiVFy2qA==", + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz", + "integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==", "dev": true }, "enhanced-resolve": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz", - "integrity": "sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, "peer": true, "requires": { @@ -4527,9 +4638,9 @@ } }, "es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", "dev": true, "peer": true }, @@ -4702,15 +4813,15 @@ "optional": true }, "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true }, "eslint-scope": { @@ -4815,15 +4926,15 @@ "dev": true }, "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, "graphql": { - "version": "16.4.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.4.0.tgz", - "integrity": "sha512-tYDNcRvKCcfHREZYje3v33NSrSD/ZpbWWdPtBtUUuXx9NCo/2QDxYzNqCnMvfsrnbwRpEHMovVrPu/ERoLrIRg==" + "version": "16.9.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz", + "integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==" }, "graphql-tag": { "version": "2.12.6", @@ -4845,7 +4956,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true }, "history": { @@ -4938,10 +5049,10 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true, "peer": true }, @@ -4961,9 +5072,9 @@ } }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "klona": { @@ -5011,13 +5122,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true, - "peer": true - }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -5047,9 +5151,9 @@ }, "dependencies": { "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "optional": true } @@ -5092,9 +5196,9 @@ } }, "moment": { - "version": "2.29.3", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz", - "integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==" + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==" }, "ms": { "version": "2.1.2", @@ -5103,9 +5207,9 @@ "dev": true }, "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true }, "needle": { @@ -5150,9 +5254,9 @@ } }, "node-releases": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.4.tgz", - "integrity": "sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", "dev": true }, "object-assign": { @@ -5202,9 +5306,9 @@ "dev": true }, "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", "dev": true }, "picomatch": { @@ -5221,14 +5325,14 @@ "optional": true }, "postcss": { - "version": "8.4.13", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.13.tgz", - "integrity": "sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==", + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", "dev": true, "requires": { - "nanoid": "^3.3.3", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" } }, "prop-types": { @@ -5249,9 +5353,9 @@ "optional": true }, "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "peer": true }, @@ -5772,9 +5876,9 @@ } }, "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, "peer": true, "requires": { @@ -5792,15 +5896,15 @@ } }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true }, "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "peer": true, "requires": { @@ -5819,9 +5923,9 @@ "dev": true }, "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true }, "source-map-support": { @@ -5868,42 +5972,30 @@ "peer": true }, "terser": { - "version": "5.13.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.13.1.tgz", - "integrity": "sha512-hn4WKOfwnwbYfe48NgrQjqNOH9jzLqRcIfbYytOXCOv46LBfWr9bDS17MQqOi+BWGD0sJK3Sj5NC/gJjiojaoA==", + "version": "5.31.6", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz", + "integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==", "dev": true, "peer": true, "requires": { - "acorn": "^8.5.0", + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", "commander": "^2.20.0", - "source-map": "~0.8.0-beta.0", "source-map-support": "~0.5.20" - }, - "dependencies": { - "source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "dev": true, - "peer": true, - "requires": { - "whatwg-url": "^7.0.0" - } - } } }, "terser-webpack-plugin": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz", - "integrity": "sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "dev": true, "peer": true, "requires": { + "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1", - "terser": "^5.7.2" + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" } }, "to-fast-properties": { @@ -5917,16 +6009,6 @@ "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=" }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "dev": true, - "peer": true, - "requires": { - "punycode": "^2.1.0" - } - }, "ts-invariant": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.10.2.tgz", @@ -5940,6 +6022,23 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, + "undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "peer": true + }, + "update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "dev": true, + "requires": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + } + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -5962,16 +6061,16 @@ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, "vite": { - "version": "2.9.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.8.tgz", - "integrity": "sha512-zsBGwn5UT3YS0NLSJ7hnR54+vUKfgzMUh/Z9CxF1YKEBVIe213+63jrFLmZphgGI5zXwQCSmqIdbPuE8NJywPw==", + "version": "2.9.18", + "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.18.tgz", + "integrity": "sha512-sAOqI5wNM9QvSEE70W3UGMdT8cyEn0+PmJMTFvTB8wB0YbYUWw3gUbY62AOyrXosGieF2htmeLATvNxpv/zNyQ==", "dev": true, "requires": { "esbuild": "^0.14.27", "fsevents": "~2.3.2", "postcss": "^8.4.13", "resolve": "^1.22.0", - "rollup": "^2.59.0" + "rollup": ">=2.59.0 <2.78.0" } }, "vite-plugin-imp": { @@ -6041,9 +6140,9 @@ } }, "watchpack": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", - "integrity": "sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", "dev": true, "peer": true, "requires": { @@ -6051,43 +6150,35 @@ "graceful-fs": "^4.1.2" } }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true, - "peer": true - }, "webpack": { - "version": "5.72.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.72.0.tgz", - "integrity": "sha512-qmSmbspI0Qo5ld49htys8GY9XhS9CGqFoHTsOVAnjBdg0Zn79y135R+k4IR4rKK6+eKaabMhJwiVB7xw0SJu5w==", + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, "peer": true, "requires": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.4.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.7.1", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.9.2", - "es-module-lexer": "^0.9.0", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-better-errors": "^1.0.2", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", + "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.3.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" } }, @@ -6098,18 +6189,6 @@ "dev": true, "peer": true }, - "whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "peer": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, "zen-observable": { "version": "0.8.15", "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", diff --git a/ui/src/Base.jsx b/ui/src/Base.jsx index cd0b139..d55995a 100644 --- a/ui/src/Base.jsx +++ b/ui/src/Base.jsx @@ -1,4 +1,4 @@ -import { Navigate, useLocation } from 'react-router-dom'; +import { Navigate, useLocation, useNavigate } from 'react-router-dom'; import { Layout, Typography, Space, Button, Row, Col } from 'antd'; const { Header } = Layout; const { Link } = Typography; @@ -18,6 +18,14 @@ function Base({ children }) { return } + const navigate = useNavigate(); + const queryParams = new URLSearchParams(location.search); + + const setTabToUrl = (name) => { + queryParams.set('tab', name); + navigate(`/?${queryParams.toString()}`); + } + return ( - + + + {auth.authenticated && + ); +} + +const CancelButton = ({ onClick, disabled }) => { + const style = disabled ? {} : { background: '#ff4d00', color: '#fff' }; + return ( + + ); +} +const DeleteButton = ({ onClick }) => { + const { name } = useValueState() + + function confirm() { + onClick(); + message.success(`Value ${name} deleted`); + } + + return ( + + + + ); +} + +const Buttons = ({ onReset, onCancel, onSave, onDelete, onToggle, onValueOverrideChange }) => { + const { dirty, overridden, enabled, value_override } = useValueState(); + return ( + + + + + + {overridden ? + overriden : 'default'} + + + + + reset + + + + + + + + +
Current value
+ +
+ +
+ ); +} + +const ValueName = ({ name }) => { + const copyValue = () => { + copyToClipboard(name, `Value ${name} copied to clipboard`); + } + + return ( +
+ + + {name} + +
+ ) +} + +const getInitialValueState = (value) => ({ + valueId: value.id, + name: value.name, + dirty: false, + overridden: value.overridden, + enabled: value.enabled, + value_default: value.value_default, + value_override: value.value_override, + // TODO sort conditions, because after save, the order is not guaranteed now + conditions: value.conditions.map((c) => c.id), +}); + +const getInitialConditions = (value) => { + return value.conditions.reduce((acc, c) => { + acc[c.id] = { + ...c, + checks: c.checks.map((check) => check.id), + value_override: c.value_override, + }; + return acc; + }, {}); +}; + +const getInitialChecks = (value, project) => { + const getKind = (check) => { + const variableId = check.variable.id + const variable = project.variablesMap[variableId]; + const variableType = variable ? variable.type : undefined; + return TYPE_TO_KIND[variableType] + }; + + return value.conditions.reduce((acc, c) => { + c.checks.forEach((check) => { + acc[check.id] = { + ...check, + kind: getKind(check), + value_timestamp: check.value_timestamp + ? moment(check.value_timestamp) + : null, + variable: check.variable.id, + }; + }); + return acc; + }, {}); +}; + + +export const Value = ({ value }) => { + const project = useProject(); + const [valueState, setValueState] = useState(getInitialValueState(value)); + const [conditions, setConditions] = useState(getInitialConditions(value)); + const [checks, setChecks] = useState(getInitialChecks(value, project)); + const { + saveValue, + saveValueFailed, + resetValue, + deleteValue, + } = useValueActions(value); + + useEffect(() => { + setValueState(getInitialValueState(value)); + setConditions(getInitialConditions(value)); + setChecks(getInitialChecks(value, project)); + }, [value]); + + const updateValue = (opts, cb = null) => { + const data = typeof opts === 'function' ? opts(valueState) : opts; + const _value = { + ...valueState, + ...data, + dirty: true + }; + setValueState(_value); + if (cb) cb(_value); + } + + const newTempCondition = () => { + // creating empty check for new condition + const check = newTempCheck(); + _setCheck(check); + + return { + id: uniqueId('temp'), + checks: [check.id], + } + } + + const newTempCheck = () => { + return { + id: uniqueId('temp'), + kind: undefined, + } + } + + const _setCondition = (condition) => { + setConditions({ + ...conditions, + [condition.id]: condition + }); + } + + const _setCheck = (check) => { + setChecks({ + ...checks, + [check.id]: check + }); + } + + const touchCondition = (conditionId) => { + let condition = conditions[conditionId]; + + if (!startsWith(conditionId, 'temp')) { + condition = assign(cloneDeep(condition), { id: uniqueId('temp') }); + + _setCondition(condition); + let valueConditions = valueState.conditions; + replaceValueInArray(valueConditions, conditionId, condition.id); + updateValue({ conditions: valueConditions }) + } + return condition; + } + + const touchCheck = (conditionId, checkId) => { + let check = checks[checkId]; + + if (!startsWith(checkId, 'temp')) { + check = assign(cloneDeep(check), { id: uniqueId('temp') }); + + _setCheck(check); + let condition = touchCondition(conditionId); + replaceValueInArray(condition.checks, checkId, check.id); + _setCondition(condition); + } + + return check; + } + + const setVariable = (conditionId, checkId, variableId) => { + let check = touchCheck(conditionId, checkId); + check.variable = variableId; + + // maybe clear operator + const type = project.variablesMap[variableId].type; + const operators = TYPES[type].operators; + if (check.operator !== undefined && operators.indexOf(check.operator) < 0) { + check.operator = undefined; + } + + // maybe clear value + const valueType = KIND_TO_TYPE[check.kind]; + + if (type !== valueType) { + check.kind = undefined; + delete check.value_string; + delete check.value_number; + delete check.value_timestamp; + delete check.value_set; + } + + _setCheck(check); + } + + const setOperator = (conditionId, checkId, operator) => { + let check = touchCheck(conditionId, checkId); + + if (operator === 0 || operator === null) { + delete check.operator; + } else { + check.operator = operator; + } + + _setCheck(check); + } + + const _setValue = (check, kind, value) => { + if (value === null) { + delete check[kind]; + check.kind === undefined; + } else { + check[kind] = value; + check.kind = kind; + } + } + + const setValueString = (conditionId, checkId, value) => { + let check = touchCheck(conditionId, checkId); + _setValue(check, KIND.VALUE_STRING, value); + delete check.value_number; + delete check.value_timestamp; + delete check.value_set; + + _setCheck(check); + } + + const setValueNumber = (conditionId, checkId, value) => { + let check = touchCheck(conditionId, checkId); + _setValue(check, KIND.VALUE_NUMBER, value); + delete check.value_string; + delete check.value_timestamp; + delete check.value_set; + + _setCheck(check); + } + + const setValueTimestamp = (conditionId, checkId, value) => { + let check = touchCheck(conditionId, checkId); + _setValue(check, KIND.VALUE_TIMESTAMP, value); + delete check.value_string; + delete check.value_number; + delete check.value_set; + + _setCheck(check); + } + + const setValueSet = (conditionId, checkId, value) => { + let check = touchCheck(conditionId, checkId); + _setValue(check, KIND.VALUE_SET, value); + delete check.value_string; + delete check.value_number; + delete check.value_timestamp; + + _setCheck(check); + } + + const toggleEnabled = () => { + updateValue((state) => ({ enabled: !state.enabled })) + } + + const valueOverrideState = (event) => { + updateValue((state) => ({ value_override: event.target.value })) + } + + const rollbackState = () => { + setValueState(getInitialValueState(value)); + } + + const addCheck = (conditionId) => { + const check = newTempCheck(); + _setCheck(check); + let condition = touchCondition(conditionId); + condition.checks.push(check.id); + _setCondition(condition); + } + + const updateValueConditionOverride = (conditionId, valueOverride) => { + let condition = touchCondition(conditionId); + condition.value_override = valueOverride; + _setCondition(condition); + } + + const deleteCheck = (conditionId, checkId) => { + let condition = touchCondition(conditionId); + condition.checks = without(condition.checks, checkId); + _setCondition(condition); + if (condition.checks.length === 0) { + deleteCondition(condition); + } + } + + const addCondition = () => { + const newCond = newTempCondition(); + _setCondition(newCond); + updateValue((state) => ({ + conditions: state.conditions.concat(newCond.id) + })); + } + + const deleteCondition = (cond) => { + updateValue((state) => ({ + conditions: state.conditions.filter((id) => id !== cond.id) + })); + } + + const ctx = { + state: valueState, + conditions, + checks, + addCondition, + deleteCondition, + addCheck, + deleteCheck, + setVariable, + setOperator, + setValueString, + setValueNumber, + setValueTimestamp, + setValueSet, + updateValueConditionOverride, + } + + return ( + } + style={{ width: 800, borderRadius: '5px' }} + > + + saveValue( + valueState, + conditions, + checks, + project + )} + onDelete={deleteValue} + onValueOverrideChange={valueOverrideState} + /> + + + + + ); +} diff --git a/ui/src/Dashboard/Value.less b/ui/src/Dashboard/Value.less new file mode 100644 index 0000000..33d6975 --- /dev/null +++ b/ui/src/Dashboard/Value.less @@ -0,0 +1,7 @@ +.value-name { + +} + +.value-name:hover { + cursor: pointer; +} diff --git a/ui/src/Dashboard/ValueCheck.jsx b/ui/src/Dashboard/ValueCheck.jsx new file mode 100644 index 0000000..fa88c13 --- /dev/null +++ b/ui/src/Dashboard/ValueCheck.jsx @@ -0,0 +1,145 @@ +import { + Button, + Col, + Input, + InputNumber, + DatePicker, + Row, + Space, + Select, +} from 'antd'; +import { + MinusOutlined, +} from '@ant-design/icons'; + +import './Check.less'; +import { Operators, TYPES, Type } from './constants'; +import { + useValueChecks, + useProject +} from './context'; + +const { Option } = Select; + +const defaultInputProps = { + style: { width: 165 }, + size: "middle" +} + +const CheckInput = ({ conditionId, check }) => { + const project = useProject(); + const { + setValueString, + setValueNumber, + setValueTimestamp, + setValueSet + } = useValueChecks(); + const variable = project.variablesMap[check.variable]; + const variableType = variable ? variable.type : undefined; + + switch (variableType) { + case Type.STRING: + return setValueString(conditionId, check.id, e.target.value)} + />; + case Type.NUMBER: + return { + setValueNumber(conditionId, check.id, value) + }} + />; + case Type.TIMESTAMP: + return { + setValueTimestamp(conditionId, check.id, date) + }} + />; + case Type.SET: + return setValueSet(conditionId, check.id, e.target.value)} + />; + } + return ; +} + +export const ValueCheck = ({ conditionId, check, onDeleteCheck, conditionValueOverride, onValueConditionOverrideChange }) => { + const project = useProject(); + const { setVariable, setOperator } = useValueChecks(); + + const onVariableOptionChange = (value) => { + setVariable(conditionId, check.id, value); + } + const onOperatorOptionChange = (value) => { + setOperator(conditionId, check.id, value); + } + + const variable = project.variablesMap[check.variable]; + const variableType = variable ? variable.type : undefined; + const operators = variableType ? TYPES[variableType].operators.map((op) => { + return { id: op, title: Operators[op].title }; + }) : []; + + return ( + + + + + + + + + + + + + + ); +} diff --git a/ui/src/Dashboard/ValueConditions.jsx b/ui/src/Dashboard/ValueConditions.jsx new file mode 100644 index 0000000..df6e603 --- /dev/null +++ b/ui/src/Dashboard/ValueConditions.jsx @@ -0,0 +1,86 @@ +import { + Button, + Col, + Row, + Space, +} from 'antd'; +import { + CloseOutlined, + PlusOutlined, +} from '@ant-design/icons'; + +import './ValueConditions.less'; +import { + useValueChecks, + useValueConditions, + useValueCtx, + useValueState, +} from './context'; +import { ValueCheck } from './ValueCheck'; + + +const ValueCondition = ({ onRemove, condition, onValueConditionOverrideChange }) => { + const { addCheck, deleteCheck } = useValueCtx(); + const { checks } = useValueChecks(); + + return ( +
+ {condition.checks.map((checkId) => { + return ( + deleteCheck(condition.id, checkId)} + conditionValueOverride={condition.value_override} + onValueConditionOverrideChange={onValueConditionOverrideChange} + /> + ) + })} + + + + + + + + +
+ ); +} + +export const ValueConditions = () => { + const value = useValueState(); + const conditions = useValueConditions(); + const { addCondition, deleteCondition, updateValueConditionOverride } = useValueCtx(); + + const noConditions = value.conditions.length === 0; + + return ( + + + {value.conditions.map((conditionId) => { + return ( + deleteCondition(conditions[conditionId])} + onValueConditionOverrideChange={(e) => updateValueConditionOverride(conditionId, e.target.value)} + /> + ) + })} + + + + ); +} diff --git a/ui/src/Dashboard/ValueConditions.less b/ui/src/Dashboard/ValueConditions.less new file mode 100644 index 0000000..8aad5f0 --- /dev/null +++ b/ui/src/Dashboard/ValueConditions.less @@ -0,0 +1,6 @@ +.condition-block { + background: #eaeaea; + border-radius: 5px; + padding: 10px; + margin-bottom: 10px; +} diff --git a/ui/src/Dashboard/Values.jsx b/ui/src/Dashboard/Values.jsx new file mode 100644 index 0000000..5ca6d35 --- /dev/null +++ b/ui/src/Dashboard/Values.jsx @@ -0,0 +1,161 @@ +import fuzzysearch from 'fuzzysearch'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { useMemo, useState, useEffect } from 'react'; + +import { + AutoComplete, + Input, + List, +} from 'antd'; +import { SearchOutlined } from '@ant-design/icons'; +import { useQuery } from '@apollo/client'; + +import './Values.less'; + +import { CenteredSpinner } from '../components/Spinner'; +import { ProjectContext } from './context'; +import { VALUES_QUERY } from './queries'; +import { Value } from './Value'; + +const getShowAllMatches = (count, searchText) => ({ + label: `Show all matches(${count})`, + value: searchText +}); + +const Values = ({ values }) => { + const location = useLocation(); + const navigate = useNavigate(); + const queryParams = new URLSearchParams(location.search); + const valueFromQuery = queryParams.get('value'); + + const [searchOptions, setSearchOptions] = useState([]); + const [searchSet, setSearchSet] = useState(null); + const [selected, setSelected] = useState(''); + + const setValueToUrl = (value) => { + if (!value) { + queryParams.delete('value'); + } else { + queryParams.set('value', value); + } + navigate(`/?${queryParams.toString()}`); + } + + useEffect(() => { + if (valueFromQuery) { + setSearchSet(new Set([valueFromQuery])); + setSelected(valueFromQuery); + } + }, [valueFromQuery]); + + const valuesMap = useMemo(() => values.reduce((acc, value) => { + acc[value.name] = value; + return acc; + }, {}), [values]); + + if (!values.length) { + return
No values
; + } + + const listData = values + .filter((value) => { + return selected ? searchSet.has(value.name) : true; + }) + .map((value) => { + return { + title: value.name, + key: value.name, + }; + }); + + /** + * filter autocomplete options + */ + const onSearch = (searchText) => { + if (!searchText) { + setSearchOptions([]); + return + }; + + const res = values + .filter(({ name }) => fuzzysearch( + searchText.toLowerCase(), name.toLowerCase() + )) + .map(({ name }) => ({ label: name, value: name })); + + setSearchOptions([getShowAllMatches(res.length, searchText)].concat(res)); + setSearchSet(new Set(res.map(({ value }) => value))); + }; + + const onSelect = (data) => { + setSelected(data); + setValueToUrl(data); + }; + + const onChange = (data) => { + if (!data) { + setSelected(''); + setSearchSet(null); + setValueToUrl(null); + } + } + + return ( +
+ + } + size="middle" + allowClear + placeholder="Filter values" + /> + + ( + + + + )} + /> +
+ ); +}; + + +export const ValuesContainer = ({ project }) => { + const { data, loading, error, networkStatus } = useQuery(VALUES_QUERY, { + variables: { project: project.name }, + }); + if (loading) { + return ; + } + + const _project = { + ...project, + variablesMap: project.variables.reduce((acc, variable) => { + acc[variable.id] = variable; + return acc; + }, {}), + } + return ( + + + + ); +} diff --git a/ui/src/Dashboard/Values.less b/ui/src/Dashboard/Values.less new file mode 100644 index 0000000..67c1ec3 --- /dev/null +++ b/ui/src/Dashboard/Values.less @@ -0,0 +1,11 @@ +.search-values { + color: #141414; + width: 800px; + position: fixed; + z-index: 90; + border: 1px solid rgba(255, 255, 0, 0.85); +} + +.values-list { + padding-top: 35px; +} diff --git a/ui/src/Dashboard/actions.js b/ui/src/Dashboard/actions.js index ffa0b0a..4b05d0e 100644 --- a/ui/src/Dashboard/actions.js +++ b/ui/src/Dashboard/actions.js @@ -18,7 +18,11 @@ import { DELETE_FLAG_MUTATION, FLAG_QUERY, RESET_FLAG_MUTATION, - SAVE_FLAG_MUTATION + SAVE_FLAG_MUTATION, + DELETE_VALUE_MUTATION, + VALUE_QUERY, + RESET_VALUE_MUTATION, + SAVE_VALUE_MUTATION, } from './queries'; import { message } from 'antd'; import { useState } from 'react'; @@ -142,7 +146,7 @@ export function getSaveOperations(flag, editFlag, conditions, checks, variablesM } break; case Type.NUMBER: - if (!isNumber(check.value_number || !check.hasOwnProperty('value_number'))) { + if (!isNumber(check.value_number) || !check.hasOwnProperty('value_number')) { errors.internalError = true; console.log(check); } else { @@ -263,3 +267,259 @@ export const useActions = (flag) => { deleteFlag, } } + +/** + * Save value operations + */ +class ValueOperation { + static _SCOPE = uuidv4(); + + static disableValue(valueId) { + return { + type: 'disable_value', + payload: { + value_id: valueId + } + } + } + + static updateValueOverride(valueId, valueOverride) { + return { + type: 'update_value_value_override', + payload: { + value_id: valueId, + value_override: valueOverride, + } + } + } + + static enableValue(valueId) { + return { + type: 'enable_value', + payload: { + value_id: valueId + } + } + } + + static _localId(value) { + return { scope: ValueOperation._SCOPE, value: value }; + } + + static addCheck(check) { + return { + type: 'add_check', + payload: { + local_id: ValueOperation._localId(check.id), + kind: check.kind, + variable: check.variable, + operator: check.operator, + value_string: undefined, + value_number: undefined, + value_timestamp: undefined, + value_set: undefined, + } + } + } + + static addCondition(valueId, conditionId, conditionChecks, valueConditionOverride) { + return { + type: 'add_value_condition', + payload: { + value_id: valueId, + local_id: ValueOperation._localId(conditionId), + checks: conditionChecks, + value_condition_override: valueConditionOverride, + } + } + } + + static disableCondition(conditionId) { + return { + type: 'disable_value_condition', + payload: { + condition_id: conditionId + } + } + } +} + +export function getValueSaveOperations(value, editValue, conditions, checks, variablesMap) { + let ops = []; + let errors = {}; + let value_ = editValue; + + if (!value_.dirty) { + return { ops, errors }; + } + + if (value.enabled === true && value_.enabled === false) { + ops.push(ValueOperation.disableValue(value.id)); + } else if (value.enabled === false && value_.enabled === true) { + ops.push(ValueOperation.enableValue(value.id)); + } + + if (value.enabled === true && value.value_override !== value_.value_override) { + ops.push(ValueOperation.updateValueOverride(value.id, value_.value_override)); + } + + const originalConditions = value.conditions.map((c) => c.id); + const newConditions = value_.conditions; + forEach(difference(newConditions, originalConditions), (conditionId) => { + let condition = conditions[conditionId]; + let conditionChecks = []; + + forEach(condition.checks, (checkId) => { + if (startsWith(checkId, 'temp')) { + const check = checks[checkId]; + if (check.kind === undefined) { + errors.missingValue = true; + return; + } + if (check.operator === null || check.operator === undefined) { + errors.missingValue = true; + return; + } + + let op = ValueOperation.addCheck(check); + let variable = variablesMap[check.variable]; + if (variable.type !== KIND_TO_TYPE[check.kind]) { + errors.internalError = true; + console.log(check, variable); + return; + } + switch (variable.type) { + case Type.STRING: + if (!isString(check.value_string) || !check.value_string) { + errors.internalError = true; + console.log(check); + } else { + op.payload.value_string = check.value_string; + } + break; + case Type.NUMBER: + if (!isNumber(check.value_number) || !check.hasOwnProperty('value_number')) { + errors.internalError = true; + console.log(check); + } else { + op.payload.value_number = check.value_number; + } + break; + case Type.TIMESTAMP: + if (!isMoment(check.value_timestamp)) { + errors.internalError = true; + console.log(check); + } else { + op.payload.value_timestamp = check.value_timestamp.unix(); + } + break; + case Type.SET: + if (!check.value_set || !check.value_set.length) { + errors.internalError = true; + console.log(check); + } else { + let items = filter(map(check.value_set.split(','), trim)); + if (items.length > 0) { + items = Array.from(new Set(items)) + } + op.payload.value_set = items; + } + break; + default: + errors.internalError = true; + console.log(variable); + } + ops.push(op); + conditionChecks.push({ + local_id: ValueOperation._localId(checkId) + }); + } else { + conditionChecks.push({ id: checkId }) + } + }); + if (condition.value_override === null || condition.value_override === undefined) { + errors.missingValue = true; + return; + } + ops.push(ValueOperation.addCondition(value.id, conditionId, conditionChecks, condition.value_override)); + + }); + forEach(difference(originalConditions, newConditions), (conditionId) => { + ops.push(ValueOperation.disableCondition(conditionId)); + }); + return { ops, errors }; +} + + +export const useValueActions = (value) => { + const [saveValueFailed, setSaveValueFailed] = useState(false); + + const [saveValueMut] = useMutation(SAVE_VALUE_MUTATION, { + refetchQueries: [{ + query: VALUE_QUERY, + variables: { + id: value.id, + } + }] + }); + const [resetValueMut] = useMutation(RESET_VALUE_MUTATION, { + variables: { + id: value.id + }, + refetchQueries: [{ + query: VALUE_QUERY, + variables: { + id: value.id + } + }] + }); + const [deleteValueMut] = useMutation(DELETE_VALUE_MUTATION, { + variables: { + id: value.id + }, + update(cache) { + const normalizedId = cache.identify({ id: value.id, __typename: 'Value' }); + cache.evict({ id: normalizedId }); + cache.gc(); + } + }); + + const saveValue = (valueState, conditions, checks, project) => { + const { + ops, + errors + } = getValueSaveOperations(value, valueState, conditions, checks, project.variablesMap); + if (!isEmpty(errors)) { + if (errors.internalError) { + message.error(INTERNAL_ERROR); + } else if (errors.missingValue) { + message.error(MISSING_VALUE); + } + setSaveValueFailed(true) + setTimeout(() => { + setSaveValueFailed(false) + }, 500); + return; + } + saveValueMut({ + variables: { + operations: ops + } + }); + } + + const resetValue = () => { + resetValueMut(); + } + + const deleteValue = () => { + deleteValueMut(); + } + + return { + saveValue, + saveValueFailed, + resetValue, + deleteValue, + } +} diff --git a/ui/src/Dashboard/context.jsx b/ui/src/Dashboard/context.jsx index 82249a5..f9e62ac 100644 --- a/ui/src/Dashboard/context.jsx +++ b/ui/src/Dashboard/context.jsx @@ -28,3 +28,24 @@ export const useChecks = () => { setValueString, setValueNumber, setValueTimestamp, setValueSet }; } + +export const ValueContext = createContext({}); +export const useValueState = () => { + const { state } = useContext(ValueContext); + return state; +} +export const useValueCtx = () => useContext(ValueContext); +export const useValueConditions = () => { + const { conditions } = useContext(ValueContext); + return conditions; +} +export const useValueChecks = () => { + const { + checks, setVariable, setOperator, setValueString, setValueNumber, + setValueTimestamp, setValueSet + } = useContext(ValueContext); + return { + checks, setVariable, setOperator, setValueString, setValueNumber, + setValueTimestamp, setValueSet + }; +} \ No newline at end of file diff --git a/ui/src/Dashboard/queries.js b/ui/src/Dashboard/queries.js index 7cd66d3..d73926a 100644 --- a/ui/src/Dashboard/queries.js +++ b/ui/src/Dashboard/queries.js @@ -82,3 +82,74 @@ export const FLAGS_QUERY = gql` } } `; + +const VALUE_FRAGMENT = gql` + fragment ValueFragment on Value { + id + name + enabled + overridden + value_default + value_override + conditions { + id + value_override + checks { + id + variable { + id + name + type + } + operator + value_string + value_number + value_timestamp + value_set + } + } + } +`; + + +export const SAVE_VALUE_MUTATION = gql` + mutation SaveValue($operations: [SaveValueOperation!]!) { + saveValue(operations: $operations) { + errors + } + } +`; + +export const RESET_VALUE_MUTATION = gql` + mutation ResetValue($id: String!) { + resetValue(id: $id) { + error + } + } +`; + +export const DELETE_VALUE_MUTATION = gql` + mutation DeleteValue($id: String!) { + deleteValue(id: $id) { + error + } + } +`; + +export const VALUE_QUERY = gql` + ${VALUE_FRAGMENT} + query Value($id: String!) { + value(id: $id) { + ...ValueFragment + } + } +`; + +export const VALUES_QUERY = gql` + ${VALUE_FRAGMENT} + query Values($project: String!) { + values(project_name: $project) { + ...ValueFragment + } + } +`; diff --git a/ui/src/Dashboard/utils.js b/ui/src/Dashboard/utils.js new file mode 100644 index 0000000..18fe082 --- /dev/null +++ b/ui/src/Dashboard/utils.js @@ -0,0 +1,16 @@ +import { message } from "antd"; + +export function copyToClipboard(text, msg) { + navigator.clipboard.writeText(text).then(() => { + message.success(msg); + }); +} + +export function replaceValueInArray(array, value, newValue) { + let idx = array.indexOf(value); + if (idx >= 0) { + array.splice(idx, 1, newValue); + } else { + throw `Value ${value} not found in array ${array}` + } +}