From f2003aa44e79a874ac72d86721b79a8e8a10aa37 Mon Sep 17 00:00:00 2001 From: "James K. Glasbrenner" Date: Fri, 8 Nov 2024 10:43:16 -0500 Subject: [PATCH 01/22] feat: split entry_point_plugin_files table into two linking tables This update takes the entry_point_plugin_files table and splits it down into 2 separate linking tables, plugin_plugin_files and entry_point_plugins. This change allows us to treat the plugin files as entirely coupled to the plugins namespace as a container, instead of their snapshotted history being tied to updates in the entrypoint. --- ...eparate_entry_point_plugin_files_table_.py | 276 ++++++++++++++++++ src/dioptra/restapi/db/models/__init__.py | 6 +- src/dioptra/restapi/db/models/entry_points.py | 8 +- src/dioptra/restapi/db/models/plugins.py | 16 + 4 files changed, 298 insertions(+), 8 deletions(-) create mode 100644 src/dioptra/restapi/db/alembic/versions/f1d231f7ef15_separate_entry_point_plugin_files_table_.py diff --git a/src/dioptra/restapi/db/alembic/versions/f1d231f7ef15_separate_entry_point_plugin_files_table_.py b/src/dioptra/restapi/db/alembic/versions/f1d231f7ef15_separate_entry_point_plugin_files_table_.py new file mode 100644 index 000000000..7603b1552 --- /dev/null +++ b/src/dioptra/restapi/db/alembic/versions/f1d231f7ef15_separate_entry_point_plugin_files_table_.py @@ -0,0 +1,276 @@ +"""Separate entry_point_plugin_files table into 2 many-to-many tables + +Revision ID: f1d231f7ef15 +Revises: 6a75ede23821 +Create Date: 2024-11-08 09:55:20.650243 + +""" + +from typing import Annotated + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.orm import ( + DeclarativeBase, + Mapped, + MappedAsDataclass, + mapped_column, + sessionmaker, +) + +# revision identifiers, used by Alembic. +revision = "f1d231f7ef15" +down_revision = "6a75ede23821" +branch_labels = None +depends_on = None + + +# Migration data models +intpk = Annotated[ + int, + mapped_column(sa.BigInteger().with_variant(sa.Integer, "sqlite"), primary_key=True), +] +bigint = Annotated[ + int, mapped_column(sa.BigInteger().with_variant(sa.Integer, "sqlite")) +] +text_ = Annotated[str, mapped_column(sa.Text())] + + +class UpgradeBase(DeclarativeBase, MappedAsDataclass): + pass + + +class DowngradeBase(DeclarativeBase, MappedAsDataclass): + pass + + +class EntryPointPluginUpgrade(UpgradeBase): + __tablename__ = "entry_point_plugins" + + # Database fields + entry_point_resource_snapshot_id: Mapped[intpk] + plugin_resource_snapshot_id: Mapped[intpk] + + +class EntryPointPluginFileUpgrade(UpgradeBase): + __tablename__ = "entry_point_plugin_files" + + # Database fields + entry_point_resource_snapshot_id: Mapped[intpk] + plugin_resource_snapshot_id: Mapped[intpk] + plugin_file_resource_snapshot_id: Mapped[intpk] + + +class PluginPluginFileUpgrade(UpgradeBase): + __tablename__ = "plugin_plugin_files" + + # Database fields + plugin_resource_snapshot_id: Mapped[intpk] + plugin_file_resource_snapshot_id: Mapped[intpk] + + +class EntryPointPluginFileDowngrade(DowngradeBase): + __tablename__ = "entry_point_plugin_files" + + # Database fields + entry_point_resource_snapshot_id: Mapped[intpk] + plugin_resource_snapshot_id: Mapped[intpk] + plugin_file_resource_snapshot_id: Mapped[intpk] + + +class PluginPluginFileDowngrade(DowngradeBase): + __tablename__ = "plugin_plugin_files" + + # Database fields + plugin_resource_snapshot_id: Mapped[intpk] + plugin_file_resource_snapshot_id: Mapped[intpk] + + +class EntryPointPluginDowngrade(DowngradeBase): + __tablename__ = "entry_point_plugins" + + # Database fields + entry_point_resource_snapshot_id: Mapped[intpk] + plugin_resource_snapshot_id: Mapped[intpk] + + +def upgrade(): + bind = op.get_bind() + Session = sessionmaker(bind=bind) + + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "entry_point_plugins", + sa.Column( + "entry_point_resource_snapshot_id", + sa.BigInteger().with_variant(sa.Integer(), "sqlite"), + nullable=False, + ), + sa.Column( + "plugin_resource_snapshot_id", + sa.BigInteger().with_variant(sa.Integer(), "sqlite"), + nullable=False, + ), + sa.ForeignKeyConstraint( + ["entry_point_resource_snapshot_id"], + ["entry_points.resource_snapshot_id"], + name=op.f( + "fk_entry_point_plugins_entry_point_resource_snapshot_id_entry_points" + ), + ), + sa.ForeignKeyConstraint( + ["plugin_resource_snapshot_id"], + ["plugins.resource_snapshot_id"], + name=op.f("fk_entry_point_plugins_plugin_resource_snapshot_id_plugins"), + ), + sa.PrimaryKeyConstraint( + "entry_point_resource_snapshot_id", + "plugin_resource_snapshot_id", + name=op.f("pk_entry_point_plugins"), + ), + ) + op.create_table( + "plugin_plugin_files", + sa.Column( + "plugin_resource_snapshot_id", + sa.BigInteger().with_variant(sa.Integer(), "sqlite"), + nullable=False, + ), + sa.Column( + "plugin_file_resource_snapshot_id", + sa.BigInteger().with_variant(sa.Integer(), "sqlite"), + nullable=False, + ), + sa.ForeignKeyConstraint( + ["plugin_file_resource_snapshot_id"], + ["plugin_files.resource_snapshot_id"], + name=op.f( + "fk_plugin_plugin_files_plugin_file_resource_snapshot_id_plugin_files" + ), + ), + sa.ForeignKeyConstraint( + ["plugin_resource_snapshot_id"], + ["plugins.resource_snapshot_id"], + name=op.f("fk_plugin_plugin_files_plugin_resource_snapshot_id_plugins"), + ), + sa.PrimaryKeyConstraint( + "plugin_resource_snapshot_id", + "plugin_file_resource_snapshot_id", + name=op.f("pk_plugin_plugin_files"), + ), + ) + + with Session() as session: + entry_point_plugin_stmt = sa.select( + EntryPointPluginFileUpgrade.entry_point_resource_snapshot_id, + EntryPointPluginFileUpgrade.plugin_resource_snapshot_id, + ).distinct() + + for row in session.execute(entry_point_plugin_stmt): + session.add( + EntryPointPluginUpgrade( + entry_point_resource_snapshot_id=row.entry_point_resource_snapshot_id, + plugin_resource_snapshot_id=row.plugin_resource_snapshot_id, + ) + ) + + plugin_plugin_files_stmt = sa.select( + EntryPointPluginFileUpgrade.plugin_resource_snapshot_id, + EntryPointPluginFileUpgrade.plugin_file_resource_snapshot_id, + ).distinct() + + for row in session.execute(plugin_plugin_files_stmt): + session.add( + PluginPluginFileUpgrade( + plugin_resource_snapshot_id=row.plugin_resource_snapshot_id, + plugin_file_resource_snapshot_id=row.plugin_file_resource_snapshot_id, + ) + ) + + session.commit() + + op.drop_table("entry_point_plugin_files") + # ### end Alembic commands ### + + +def downgrade(): + bind = op.get_bind() + Session = sessionmaker(bind=bind) + + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "entry_point_plugin_files", + sa.Column( + "entry_point_resource_snapshot_id", + sa.BigInteger().with_variant(sa.Integer(), "sqlite"), + nullable=False, + ), + sa.Column( + "plugin_resource_snapshot_id", + sa.BigInteger().with_variant(sa.Integer(), "sqlite"), + nullable=False, + ), + sa.Column( + "plugin_file_resource_snapshot_id", + sa.BigInteger().with_variant(sa.Integer(), "sqlite"), + nullable=False, + ), + sa.PrimaryKeyConstraint( + "entry_point_resource_snapshot_id", + "plugin_resource_snapshot_id", + "plugin_file_resource_snapshot_id", + name=op.f("pk_entry_point_plugin_files"), + ), + ) + with op.batch_alter_table("entry_point_plugin_files", schema=None) as batch_op: + batch_op.create_foreign_key( + batch_op.f( + "fk_entry_point_plugin_files_entry_point_resource_snapshot_id_entry_points" + ), + "entry_points", + ["entry_point_resource_snapshot_id"], + ["resource_snapshot_id"], + ) + batch_op.create_foreign_key( + batch_op.f( + "fk_entry_point_plugin_files_plugin_file_resource_snapshot_id_plugin_files" + ), + "plugin_files", + ["plugin_file_resource_snapshot_id"], + ["resource_snapshot_id"], + ) + batch_op.create_foreign_key( + batch_op.f( + "fk_entry_point_plugin_files_plugin_resource_snapshot_id_plugins" + ), + "plugins", + ["plugin_resource_snapshot_id"], + ["resource_snapshot_id"], + ) + + with Session() as session: + entry_point_plugin_files_stmt = sa.select( + EntryPointPluginDowngrade.entry_point_resource_snapshot_id, + EntryPointPluginDowngrade.plugin_resource_snapshot_id, + PluginPluginFileDowngrade.plugin_file_resource_snapshot_id, + ).join_from( + PluginPluginFileDowngrade, + EntryPointPluginDowngrade, + EntryPointPluginDowngrade.plugin_resource_snapshot_id + == PluginPluginFileDowngrade.plugin_resource_snapshot_id, + ) + + for row in session.execute(entry_point_plugin_files_stmt): + session.add( + EntryPointPluginFileDowngrade( + entry_point_resource_snapshot_id=row.entry_point_resource_snapshot_id, + plugin_resource_snapshot_id=row.plugin_resource_snapshot_id, + plugin_file_resource_snapshot_id=row.plugin_file_resource_snapshot_id, + ) + ) + + session.commit() + + op.drop_table("plugin_plugin_files") + op.drop_table("entry_point_plugins") + # ### end Alembic commands ### diff --git a/src/dioptra/restapi/db/models/__init__.py b/src/dioptra/restapi/db/models/__init__.py index 794066517..17bb3b737 100644 --- a/src/dioptra/restapi/db/models/__init__.py +++ b/src/dioptra/restapi/db/models/__init__.py @@ -19,7 +19,7 @@ EntryPoint, EntryPointParameter, EntryPointParameterValue, - EntryPointPluginFile, + EntryPointPlugin, entry_point_parameter_types_table, ) from .experiments import Experiment @@ -44,6 +44,7 @@ from .plugins import ( Plugin, PluginFile, + PluginPluginFile, PluginTask, PluginTaskInputParameter, PluginTaskOutputParameter, @@ -71,7 +72,7 @@ "EntryPointJob", "EntryPointParameter", "EntryPointParameterValue", - "EntryPointPluginFile", + "EntryPointPlugin", "Experiment", "ExperimentJob", "Group", @@ -84,6 +85,7 @@ "MlModelVersion", "Plugin", "PluginFile", + "PluginPluginFile", "PluginTask", "PluginTaskInputParameter", "PluginTaskOutputParameter", diff --git a/src/dioptra/restapi/db/models/entry_points.py b/src/dioptra/restapi/db/models/entry_points.py index b7085792b..9def0e843 100644 --- a/src/dioptra/restapi/db/models/entry_points.py +++ b/src/dioptra/restapi/db/models/entry_points.py @@ -146,8 +146,8 @@ class EntryPointParameterValue(db.Model): # type: ignore[name-defined] ) -class EntryPointPluginFile(db.Model): # type: ignore[name-defined] - __tablename__ = "entry_point_plugin_files" +class EntryPointPlugin(db.Model): # type: ignore[name-defined] + __tablename__ = "entry_point_plugins" # Database fields entry_point_resource_snapshot_id: Mapped[intpk] = mapped_column( @@ -156,13 +156,9 @@ class EntryPointPluginFile(db.Model): # type: ignore[name-defined] plugin_resource_snapshot_id: Mapped[intpk] = mapped_column( ForeignKey("plugins.resource_snapshot_id"), init=False ) - plugin_file_resource_snapshot_id: Mapped[intpk] = mapped_column( - ForeignKey("plugin_files.resource_snapshot_id"), init=False - ) # Relationships entry_point: Mapped["EntryPoint"] = relationship( back_populates="entry_point_plugin_files", lazy="joined" ) plugin: Mapped["Plugin"] = relationship(lazy="joined") - plugin_file: Mapped["PluginFile"] = relationship(lazy="joined") diff --git a/src/dioptra/restapi/db/models/plugins.py b/src/dioptra/restapi/db/models/plugins.py index debe156a8..fc6294d12 100644 --- a/src/dioptra/restapi/db/models/plugins.py +++ b/src/dioptra/restapi/db/models/plugins.py @@ -102,6 +102,22 @@ class PluginFile(ResourceSnapshot): } +class PluginPluginFile(db.Model): # type: ignore[name-defined] + __tablename__ = "plugin_plugin_files" + + # Database fields + plugin_resource_snapshot_id: Mapped[intpk] = mapped_column( + ForeignKey("plugins.resource_snapshot_id"), init=False + ) + plugin_file_resource_snapshot_id: Mapped[intpk] = mapped_column( + ForeignKey("plugin_files.resource_snapshot_id"), init=False + ) + + # Relationships + plugin: Mapped["Plugin"] = relationship(lazy="joined") + plugin_file: Mapped["PluginFile"] = relationship(lazy="joined") + + class PluginTask(db.Model): # type: ignore[name-defined] __tablename__ = "plugin_tasks" From 60b5e74db321a745b7ad42759e83606ec91d1959 Mon Sep 17 00:00:00 2001 From: henrychoy Date: Tue, 19 Nov 2024 12:09:23 -0500 Subject: [PATCH 02/22] fix: update service for entrypoints --- src/dioptra/restapi/db/models/entry_points.py | 4 +- src/dioptra/restapi/v1/entrypoints/service.py | 101 ++++++++++-------- src/dioptra/restapi/v1/utils.py | 27 +++-- .../v1/workflows/lib/export_plugin_files.py | 2 +- .../workflows/lib/export_task_engine_yaml.py | 6 +- .../v1/workflows/lib/package_job_files.py | 2 +- src/dioptra/restapi/v1/workflows/lib/views.py | 14 ++- 7 files changed, 90 insertions(+), 66 deletions(-) diff --git a/src/dioptra/restapi/db/models/entry_points.py b/src/dioptra/restapi/db/models/entry_points.py index 9def0e843..e90f7b617 100644 --- a/src/dioptra/restapi/db/models/entry_points.py +++ b/src/dioptra/restapi/db/models/entry_points.py @@ -55,7 +55,7 @@ class EntryPoint(ResourceSnapshot): entry_point_jobs: Mapped[list["EntryPointJob"]] = relationship( init=False, viewonly=True ) - entry_point_plugin_files: Mapped[list["EntryPointPluginFile"]] = relationship( + entry_point_plugins: Mapped[list["EntryPointPlugin"]] = relationship( init=False, back_populates="entry_point" ) @@ -159,6 +159,6 @@ class EntryPointPlugin(db.Model): # type: ignore[name-defined] # Relationships entry_point: Mapped["EntryPoint"] = relationship( - back_populates="entry_point_plugin_files", lazy="joined" + back_populates="entry_point_plugins", lazy="joined" ) plugin: Mapped["Plugin"] = relationship(lazy="joined") diff --git a/src/dioptra/restapi/v1/entrypoints/service.py b/src/dioptra/restapi/v1/entrypoints/service.py index a8f3cf711..17b583644 100644 --- a/src/dioptra/restapi/v1/entrypoints/service.py +++ b/src/dioptra/restapi/v1/entrypoints/service.py @@ -145,16 +145,15 @@ def create( creator=current_user, ) - entry_point_plugin_files = [ - models.EntryPointPluginFile( + entry_point_plugins = [ + models.EntryPointPlugin( entry_point=new_entrypoint, plugin=plugin["plugin"], - plugin_file=plugin_file, ) for plugin in plugins - for plugin_file in plugin["plugin_files"] ] - new_entrypoint.entry_point_plugin_files = entry_point_plugin_files + + new_entrypoint.entry_point_plugins = entry_point_plugins plugin_resources = [plugin["plugin"].resource for plugin in plugins] queue_resources = [queue.resource for queue in queues] @@ -467,19 +466,18 @@ def modify( resource=entrypoint.resource, creator=current_user, ) - new_entrypoint.entry_point_plugin_files = [ - models.EntryPointPluginFile( + new_entrypoint.entry_point_plugins = [ + models.EntryPointPlugin( entry_point=new_entrypoint, - plugin=entry_point_plugin_file.plugin, - plugin_file=entry_point_plugin_file.plugin_file, + plugin=entry_point_plugin.plugin, ) - for entry_point_plugin_file in entrypoint.entry_point_plugin_files + for entry_point_plugin in entrypoint.entry_point_plugins ] plugin_resources = list( { plugin.plugin.resource_id: plugin.plugin.resource - for plugin in new_entrypoint.entry_point_plugin_files + for plugin in new_entrypoint.entry_point_plugins }.values() ) queue_resources = [queue.resource for queue in queues] @@ -630,34 +628,31 @@ def append( creator=current_user, ) - new_entry_point_plugin_files = [ - models.EntryPointPluginFile( + new_entry_point_plugins = [ + models.EntryPointPlugin( entry_point=new_entrypoint, plugin=plugin["plugin"], - plugin_file=plugin_file, ) for plugin in self._plugin_ids_service.get( plugin_ids, error_if_not_found=True ) - for plugin_file in plugin["plugin_files"] ] - existing_entry_point_plugin_files = [ - models.EntryPointPluginFile( + existing_entry_point_plugins = [ + models.EntryPointPlugin( entry_point=new_entrypoint, - plugin=entry_point_plugin_file.plugin, - plugin_file=entry_point_plugin_file.plugin_file, + plugin=entry_point_plugin.plugin, ) - for entry_point_plugin_file in entrypoint.entry_point_plugin_files - if entry_point_plugin_file.plugin.resource_id not in set(plugin_ids) + for entry_point_plugin in entrypoint.entry_point_plugins + if entry_point_plugin.plugin.resource_id not in set(plugin_ids) ] - new_entrypoint.entry_point_plugin_files = ( - existing_entry_point_plugin_files + new_entry_point_plugin_files + new_entrypoint.entry_point_plugins = ( + existing_entry_point_plugins + new_entry_point_plugins ) plugin_resources = list( { plugin.plugin.resource_id: plugin.plugin.resource - for plugin in new_entrypoint.entry_point_plugin_files + for plugin in new_entrypoint.entry_point_plugins }.values() ) queue_resources = [ @@ -728,20 +723,27 @@ def get( )["entry_point"] plugins = { - entry_point_plugin_file.plugin.resource_id: entry_point_plugin_file.plugin - for entry_point_plugin_file in entrypoint.entry_point_plugin_files + entry_point_plugin.plugin.resource_id: entry_point_plugin.plugin + for entry_point_plugin in entrypoint.entry_point_plugins } if plugin_id not in plugins: raise EntityDoesNotExistError( PLUGIN_RESOURCE_TYPE, entrypoint_id=entrypoint_id, plugin_id=plugin_id ) + plugin_files_stmt = ( + select(models.PluginFile) + .where( + models.PluginFile.plugin_id + == plugin_id + ) + ) + plugin_files = db.session.scalars(plugin_files_stmt).unique().all() + + # Construct the PluginWithFilesDict plugin = utils.PluginWithFilesDict( - plugin=plugins[plugin_id], plugin_files=[], has_draft=None + plugin=plugins[plugin_id], plugin_files=plugin_files, has_draft=None ) - for entry_point_plugin_file in entrypoint.entry_point_plugin_files: - if entry_point_plugin_file.plugin.resource_id == plugin_id: - plugin["plugin_files"].append(entry_point_plugin_file.plugin_file) return plugin @@ -775,7 +777,7 @@ def delete( )["entry_point"] plugin_ids = set( - plugin.plugin.resource_id for plugin in entrypoint.entry_point_plugin_files + plugin.plugin.resource_id for plugin in entrypoint.entry_point_plugins ) if plugin_id not in plugin_ids: raise EntityDoesNotExistError( @@ -800,14 +802,13 @@ def delete( resource=entrypoint.resource, creator=current_user, ) - new_entrypoint.entry_point_plugin_files = [ - models.EntryPointPluginFile( + new_entrypoint.entry_point_plugins = [ + models.EntryPointPlugin( entry_point=new_entrypoint, - plugin=entry_point_plugin_file.plugin, - plugin_file=entry_point_plugin_file.plugin_file, + plugin=entry_point_plugin.plugin, ) - for entry_point_plugin_file in entrypoint.entry_point_plugin_files - if entry_point_plugin_file.plugin.resource_id != plugin_id + for entry_point_plugin in entrypoint.entry_point_plugins + if entry_point_plugin.plugin.resource_id != plugin_id ] # remove the plugin resource dependency association @@ -1194,13 +1195,25 @@ def _get_entrypoint_plugin_snapshots( entrypoint: models.EntryPoint, ) -> list[utils.PluginWithFilesDict]: plugins_dict = { - entry_point_plugin_file.plugin.resource_id: utils.PluginWithFilesDict( - plugin=entry_point_plugin_file.plugin, plugin_files=[], has_draft=False + entry_point_plugin.plugin.resource_id: utils.PluginWithFilesDict( + plugin=entry_point_plugin.plugin, plugin_files=[], has_draft=False ) - for entry_point_plugin_file in entrypoint.entry_point_plugin_files + for entry_point_plugin in entrypoint.entry_point_plugins } - for entry_point_plugin_file in entrypoint.entry_point_plugin_files: - resource_id = entry_point_plugin_file.plugin.resource_id - plugin_file = entry_point_plugin_file.plugin_file - plugins_dict[resource_id]["plugin_files"].append(plugin_file) + for entry_point_plugin in entrypoint.entry_point_plugins: + resource_id = entry_point_plugin.plugin.resource_id + plugin_files = db.session.scalars( + select(models.PluginFile) + .join(models.PluginPluginFile, + models.PluginPluginFile.plugin_file_resource_snapshot_id + == models.PluginFile.resource_snapshot_id + ) + .where( + models.PluginPluginFile.plugin_resource_snapshot_id + == entry_point_plugin.plugin.resource_snapshot_id + ) + ).all() + + plugins_dict[resource_id]["plugin_files"].extend(plugin_files) + return list(plugins_dict.values()) diff --git a/src/dioptra/restapi/v1/utils.py b/src/dioptra/restapi/v1/utils.py index 27c9f41f2..ac4d1274c 100644 --- a/src/dioptra/restapi/v1/utils.py +++ b/src/dioptra/restapi/v1/utils.py @@ -20,9 +20,11 @@ from marshmallow import Schema -from dioptra.restapi.db import models +from dioptra.restapi.db import models, db from dioptra.restapi.routes import V1_ROOT +from sqlalchemy import select + ARTIFACTS: Final[str] = "artifacts" ENTRYPOINTS: Final[str] = "entrypoints" EXPERIMENTS: Final[str] = "experiments" @@ -587,17 +589,20 @@ def build_entrypoint(entrypoint_dict: EntrypointDict) -> dict[str, Any]: queues = entrypoint_dict.get("queues", None) has_draft = entrypoint_dict.get("has_draft", None) - plugins_dict = { - entry_point_plugin_file.plugin.resource_id: PluginWithFilesDict( - plugin=entry_point_plugin_file.plugin, plugin_files=[], has_draft=False + plugins = [ + PluginWithFilesDict( + plugin=entry_point_plugin.plugin, + plugin_files=db.session.scalars( + select(models.PluginFile) + .where( + models.PluginFile.plugin_id + == entry_point_plugin.plugin.resource_id + ) + ).unique().all(), + has_draft=False, ) - for entry_point_plugin_file in entrypoint.entry_point_plugin_files - } - for entry_point_plugin_file in entrypoint.entry_point_plugin_files: - resource_id = entry_point_plugin_file.plugin.resource_id - plugin_file = entry_point_plugin_file.plugin_file - plugins_dict[resource_id]["plugin_files"].append(plugin_file) - plugins = list(plugins_dict.values()) + for entry_point_plugin in entrypoint.entry_point_plugins + ] data = { "id": entrypoint.resource_id, diff --git a/src/dioptra/restapi/v1/workflows/lib/export_plugin_files.py b/src/dioptra/restapi/v1/workflows/lib/export_plugin_files.py index 4a3dd3bcd..afb7990da 100644 --- a/src/dioptra/restapi/v1/workflows/lib/export_plugin_files.py +++ b/src/dioptra/restapi/v1/workflows/lib/export_plugin_files.py @@ -25,7 +25,7 @@ def export_plugin_files( - entry_point_plugin_files: list[models.EntryPointPluginFile], + entry_point_plugin_files: list[models.PluginPluginFile], plugins_base_dir: Path, logger: BoundLogger | None = None, ) -> list[Path]: diff --git a/src/dioptra/restapi/v1/workflows/lib/export_task_engine_yaml.py b/src/dioptra/restapi/v1/workflows/lib/export_task_engine_yaml.py index d89923b84..92c377a82 100644 --- a/src/dioptra/restapi/v1/workflows/lib/export_task_engine_yaml.py +++ b/src/dioptra/restapi/v1/workflows/lib/export_task_engine_yaml.py @@ -50,7 +50,7 @@ def export_task_engine_yaml( entrypoint: models.EntryPoint, - entry_point_plugin_files: list[models.EntryPointPluginFile], + entry_point_plugin_files: list[models.PluginPluginFile], plugin_parameter_types: list[models.PluginTaskParameterType], base_dir: Path, logger: BoundLogger | None = None, @@ -85,7 +85,7 @@ def export_task_engine_yaml( def build_task_engine_dict( entrypoint: models.EntryPoint, - entry_point_plugin_files: list[models.EntryPointPluginFile], + entry_point_plugin_files: list[models.PluginPluginFile], plugin_parameter_types: list[models.PluginTaskParameterType], logger: BoundLogger | None = None, ) -> dict[str, Any]: @@ -147,7 +147,7 @@ def extract_parameters( def extract_tasks( - entry_point_plugin_files: list[models.EntryPointPluginFile], + entry_point_plugin_files: list[models.PluginPluginFile], plugin_parameter_types: list[models.PluginTaskParameterType], logger: BoundLogger | None = None, ) -> tuple[dict[str, Any], dict[str, Any]]: diff --git a/src/dioptra/restapi/v1/workflows/lib/package_job_files.py b/src/dioptra/restapi/v1/workflows/lib/package_job_files.py index b2c904bd0..93eade6b0 100644 --- a/src/dioptra/restapi/v1/workflows/lib/package_job_files.py +++ b/src/dioptra/restapi/v1/workflows/lib/package_job_files.py @@ -38,7 +38,7 @@ def package_job_files( job_id: int, experiment: models.Experiment, entry_point: models.EntryPoint, - entry_point_plugin_files: list[models.EntryPointPluginFile], + entry_point_plugin_files: list[models.PluginPluginFile], job_parameter_values: list[models.EntryPointParameterValue], plugin_parameter_types: list[models.PluginTaskParameterType], file_type: FileTypes, diff --git a/src/dioptra/restapi/v1/workflows/lib/views.py b/src/dioptra/restapi/v1/workflows/lib/views.py index d4bf3340d..d52ddd4f6 100644 --- a/src/dioptra/restapi/v1/workflows/lib/views.py +++ b/src/dioptra/restapi/v1/workflows/lib/views.py @@ -86,7 +86,7 @@ def get_experiment(job_id: int, logger: BoundLogger | None = None) -> models.Exp def get_entry_point_plugin_files( job_id: int, logger: BoundLogger | None = None -) -> list[models.EntryPointPluginFile]: +) -> list[models.PluginPluginFile]: """Run a query to get the plugin files for an entrypoint. Args: @@ -106,9 +106,15 @@ def get_entry_point_plugin_files( models.EntryPointJob.job_resource_id == job_id, ) ) - entry_point_plugin_files_stmt = select(models.EntryPointPluginFile).where( - models.EntryPointPluginFile.entry_point_resource_snapshot_id - == entry_point_resource_snapshot_id_stmt.scalar_subquery(), + + # Get plugins linked to the entry point + entry_point_plugins_stmt = select(models.EntryPointPlugin.plugin_resource_snapshot_id).where( + models.EntryPointPlugin.entry_point_resource_snapshot_id == entry_point_resource_snapshot_id_stmt.scalar_subquery() + ) + + # Get plugin files linked to these plugins + entry_point_plugin_files_stmt = select(models.PluginPluginFile).where( + models.PluginPluginFile.plugin_resource_snapshot_id.in_(entry_point_plugins_stmt) ) return list(db.session.scalars(entry_point_plugin_files_stmt).unique().all()) From f2ac8263dc00c6d084094c88f5f783ed3d52f376 Mon Sep 17 00:00:00 2001 From: henrychoy Date: Sat, 23 Nov 2024 20:41:21 -0500 Subject: [PATCH 03/22] fix: updated plugin service --- src/dioptra/restapi/db/models/plugins.py | 12 ++ src/dioptra/restapi/v1/entrypoints/schema.py | 7 ++ src/dioptra/restapi/v1/entrypoints/service.py | 2 +- src/dioptra/restapi/v1/plugins/service.py | 116 +++++++++++++----- src/dioptra/restapi/v1/utils.py | 18 +-- src/frontend/src/services/dataApi.ts | 2 +- 6 files changed, 114 insertions(+), 43 deletions(-) diff --git a/src/dioptra/restapi/db/models/plugins.py b/src/dioptra/restapi/db/models/plugins.py index fc6294d12..1f5d09d1b 100644 --- a/src/dioptra/restapi/db/models/plugins.py +++ b/src/dioptra/restapi/db/models/plugins.py @@ -40,6 +40,18 @@ class Plugin(ResourceSnapshot): resource_id: Mapped[bigint] = mapped_column(init=False, nullable=False, index=True) name: Mapped[text_] = mapped_column(nullable=False, index=True) + # Relationships + plugin_plugin_files: Mapped[list["PluginPluginFile"]] = relationship( + init=False, back_populates="plugin" + ) + # plugin_files: Mapped[list["PluginFile"]] = relationship( + # "PluginFile", + # secondary="plugin_plugin_files", + # primaryjoin="Plugin.resource_snapshot_id == PluginPluginFile.plugin_resource_snapshot_id", + # secondaryjoin="PluginPluginFile.plugin_file_resource_snapshot_id == PluginFile.resource_snapshot_id", + # init=False + # ) + # Additional settings __table_args__ = ( # type: ignore[assignment] Index(None, "resource_snapshot_id", "resource_id", unique=True), diff --git a/src/dioptra/restapi/v1/entrypoints/schema.py b/src/dioptra/restapi/v1/entrypoints/schema.py index af0220c11..7b4da170d 100644 --- a/src/dioptra/restapi/v1/entrypoints/schema.py +++ b/src/dioptra/restapi/v1/entrypoints/schema.py @@ -66,6 +66,13 @@ class EntrypointPluginSchema(Schema): attribute="snapshot_id", metadata=dict(description="Snapshot ID for the Plugin resource."), ) + latestSnapshot = fields.Boolean( + attribute="latest_snapshot", + metadata=dict( + description=f"Whether or not the {name} resource is the latest version." + ), + dump_only=True, + ) url = fields.Url( attribute="url", metadata=dict(description="URL for accessing the full Plugin snapshot."), diff --git a/src/dioptra/restapi/v1/entrypoints/service.py b/src/dioptra/restapi/v1/entrypoints/service.py index 17b583644..80ff2e06d 100644 --- a/src/dioptra/restapi/v1/entrypoints/service.py +++ b/src/dioptra/restapi/v1/entrypoints/service.py @@ -1212,7 +1212,7 @@ def _get_entrypoint_plugin_snapshots( models.PluginPluginFile.plugin_resource_snapshot_id == entry_point_plugin.plugin.resource_snapshot_id ) - ).all() + ).unique().all() plugins_dict[resource_id]["plugin_files"].extend(plugin_files) diff --git a/src/dioptra/restapi/v1/plugins/service.py b/src/dioptra/restapi/v1/plugins/service.py index fadb12ec4..fc2976707 100644 --- a/src/dioptra/restapi/v1/plugins/service.py +++ b/src/dioptra/restapi/v1/plugins/service.py @@ -226,36 +226,20 @@ def get( plugins = db.session.scalars(latest_plugins_stmt).all() - # extract list of plugin ids - plugin_ids = [plugin.resource_id for plugin in plugins] - - # Build CTE that retrieves all snapshot ids for the list of plugin files - # associated with retrieved plugins - parent_plugin = aliased(models.Plugin) - plugin_file_snapshot_ids_cte = ( - select(models.PluginFile.resource_snapshot_id) - .join( - models.resource_dependencies_table, - models.PluginFile.resource_id - == models.resource_dependencies_table.c.child_resource_id, - ) - .join( - parent_plugin, - parent_plugin.resource_id - == models.resource_dependencies_table.c.parent_resource_id, + plugins_dict: dict[int, utils.PluginWithFilesDict] = { + plugin.resource_id: utils.PluginWithFilesDict( + plugin=plugin, + plugin_files=[], + has_draft=False ) - .where(parent_plugin.resource_id.in_(plugin_ids)) - .cte() - ) + for plugin in plugins + } # get the latest plugin file snapshots associated with the retrieved plugins latest_plugin_files_stmt = ( select(models.PluginFile) .join(models.Resource) .where( - models.PluginFile.resource_snapshot_id.in_( - select(plugin_file_snapshot_ids_cte) - ), models.Resource.latest_snapshot_id == models.PluginFile.resource_snapshot_id, models.Resource.is_deleted == False, # noqa: E712 @@ -263,13 +247,6 @@ def get( ) plugin_files = db.session.scalars(latest_plugin_files_stmt).unique().all() - # build a dictionary structure to re-associate plugins and plugin files - plugins_dict: dict[int, utils.PluginWithFilesDict] = { - plugin.resource_id: utils.PluginWithFilesDict( - plugin=plugin, plugin_files=[], has_draft=False - ) - for plugin in plugins - } for plugin_file in plugin_files: plugins_dict[plugin_file.plugin_id]["plugin_files"].append(plugin_file) @@ -359,6 +336,8 @@ def get( ) ) plugin_files = list(db.session.scalars(latest_plugin_files_stmt).unique().all()) + # plugin_files = list construction thing + drafts_stmt = ( select(models.DraftResource.draft_resource_id) @@ -434,6 +413,13 @@ def modify( ) db.session.add(new_plugin) + for plugin_file in plugin_files: + plugin_plugin_file = models.PluginPluginFile( + plugin=new_plugin, + plugin_file=plugin_file + ) + db.session.add(plugin_plugin_file) + if commit: db.session.commit() log.debug( @@ -466,15 +452,29 @@ def delete(self, plugin_id: int, **kwargs) -> dict[str, Any]: if plugin_resource is None: raise EntityDoesNotExistError(PLUGIN_RESOURCE_TYPE, plugin_id=plugin_id) + + from dioptra.restapi.v1.plugins.service import PluginIdFileService + plugin_id_file_service = PluginIdFileService( + plugin_file_name_service=None, + group_id_service=None, + plugin_id_service=self, + ) + plugin_file_ids = plugin_id_file_service.delete(plugin_id=plugin_id)["id"] + deleted_resource_lock = models.ResourceLock( resource_lock_type=resource_lock_types.DELETE, resource=plugin_resource, ) db.session.add(deleted_resource_lock) db.session.commit() - log.debug("Plugin deleted", plugin_id=plugin_id) - return {"status": "Success", "id": [plugin_id]} + log.debug( + "Plugin and associated files deleted", + plugin_id=plugin_id, + plugin_file_ids=plugin_file_ids, + ) + + return {"status": "Success", "plugin_id": plugin_id, "file_ids": plugin_file_ids} class PluginIdsService(object): @@ -776,6 +776,8 @@ def create( new_plugin_file.parents.append(plugin.resource) db.session.add(new_plugin_file) + _update_plugin_and_file_snapshots(plugin, new_plugin_file) + _add_plugin_tasks(tasks, plugin_file=new_plugin_file, log=log) if commit: @@ -1141,6 +1143,8 @@ def modify( ) db.session.add(updated_plugin_file) + _update_plugin_and_file_snapshots(plugin, updated_plugin_file) + _add_plugin_tasks(tasks, plugin_file=updated_plugin_file, log=log) if commit: @@ -1194,6 +1198,15 @@ def delete(self, plugin_id: int, plugin_file_id: int, **kwargs) -> dict[str, Any plugin_id=plugin_id, plugin_file_id=plugin_file_id, ) + + plugin_file_dict = self.get( + plugin_id=plugin_id, + plugin_file_id=plugin_file_id, + ) + + plugin = plugin_file_dict["plugin"] + + _update_plugin_and_file_snapshots(plugin, plugin_file) plugin_file_id_to_return = plugin_file.resource_id # to return to user db.session.add( @@ -1333,3 +1346,42 @@ def _add_plugin_tasks( log=log, ) db.session.add(plugin_task) + + +def _update_plugin_and_file_snapshots( + plugin: models.Plugin, + plugin_file: models.PluginFile, + delete: bool = False, +) -> None: + + # add snapshot for plugin + new_plugin = models.Plugin( + name=plugin.name, + description=plugin.description, + resource=plugin.resource, + creator=current_user, + ) + db.session.add(new_plugin) + + if delete: + return + + # add snapshot for existing plugin_plugin_files + for plugin_plugin_file in plugin.plugin_plugin_files: + if ( + plugin_plugin_file.plugin_resource_snapshot_id == plugin.resource_snapshot_id and + plugin_plugin_file.plugin_file.resource_id != plugin_file.resource_id + ): + existing_plugin_plugin_file = models.PluginPluginFile( + plugin=new_plugin, + plugin_file=plugin_plugin_file.plugin_file + ) + db.session.add(existing_plugin_plugin_file) + + # add snapshot for new plugin_plugin_file + new_plugin_plugin_file = models.PluginPluginFile( + plugin=new_plugin, + plugin_file=plugin_file + ) + db.session.add(new_plugin_plugin_file) + diff --git a/src/dioptra/restapi/v1/utils.py b/src/dioptra/restapi/v1/utils.py index ac4d1274c..19dc96d7d 100644 --- a/src/dioptra/restapi/v1/utils.py +++ b/src/dioptra/restapi/v1/utils.py @@ -257,6 +257,7 @@ def build_entrypoint_plugin(plugin_with_files: PluginWithFilesDict) -> dict[str, return { "id": plugin.resource_id, "snapshot_id": plugin.resource_snapshot_id, + "latest_snapshot": plugin.resource.latest_snapshot_id == plugin.resource_snapshot_id, "name": plugin.name, "url": build_url( f"{PLUGINS}/{plugin.resource_id}/snapshots/{plugin.resource_snapshot_id}" @@ -592,18 +593,17 @@ def build_entrypoint(entrypoint_dict: EntrypointDict) -> dict[str, Any]: plugins = [ PluginWithFilesDict( plugin=entry_point_plugin.plugin, - plugin_files=db.session.scalars( - select(models.PluginFile) - .where( - models.PluginFile.plugin_id - == entry_point_plugin.plugin.resource_id - ) - ).unique().all(), - has_draft=False, - ) + plugin_files=[plugin_plugin_file.plugin_file for plugin_plugin_file in entry_point_plugin.plugin.plugin_plugin_files]) for entry_point_plugin in entrypoint.entry_point_plugins ] + # plugins = [ + # PluginWithFilesDict( + # plugin=entry_point_plugin.plugin, + # plugin_files=[file for file in entry_point_plugin.plugin.plugin_files]) + # for entry_point_plugin in entrypoint.entry_point_plugins + # ] + data = { "id": entrypoint.resource_id, "snapshot_id": entrypoint.resource_snapshot_id, diff --git a/src/frontend/src/services/dataApi.ts b/src/frontend/src/services/dataApi.ts index 1566748f0..1850de0d4 100644 --- a/src/frontend/src/services/dataApi.ts +++ b/src/frontend/src/services/dataApi.ts @@ -151,7 +151,7 @@ export async function getData(type: T, pagination: Paginatio Object.assign(obj, obj.payload) }) } - console.log('getData = ', res) + console.log('getData = ', res.data.data) return res } From 45f1c91da1e646845456321df999f3cbab02bb32 Mon Sep 17 00:00:00 2001 From: henrychoy Date: Tue, 26 Nov 2024 14:30:51 -0500 Subject: [PATCH 04/22] feat: add sync to latest plugin btn --- src/dioptra/restapi/v1/entrypoints/schema.py | 7 ++ src/dioptra/restapi/v1/plugins/schema.py | 40 ++++---- src/dioptra/restapi/v1/plugins/service.py | 9 +- src/dioptra/restapi/v1/utils.py | 2 + .../src/dialogs/AssignPluginsDialog.vue | 96 +++++++++++-------- src/frontend/src/views/ArtifactsView.vue | 9 -- src/frontend/src/views/CreateEntryPoint.vue | 88 +++++++++++++---- src/frontend/src/views/EntryPointsView.vue | 27 ++---- src/frontend/src/views/PluginsView.vue | 39 ++++++-- 9 files changed, 205 insertions(+), 112 deletions(-) diff --git a/src/dioptra/restapi/v1/entrypoints/schema.py b/src/dioptra/restapi/v1/entrypoints/schema.py index 7b4da170d..a59739194 100644 --- a/src/dioptra/restapi/v1/entrypoints/schema.py +++ b/src/dioptra/restapi/v1/entrypoints/schema.py @@ -18,6 +18,7 @@ from marshmallow import Schema, fields, validate from dioptra.restapi.v1.queues.schema import QueueRefSchema +from dioptra.restapi.v1.plugins.schema import PluginTaskSchema from dioptra.restapi.v1.schemas import ( BasePageSchema, GroupIdQueryParametersSchema, @@ -49,6 +50,12 @@ class EntrypointPluginFileSchema(Schema): metadata=dict(description="URL for accessing the full PluginFile snapshot."), relative=True, ) + tasks = fields.Nested( + PluginTaskSchema, + attribute="tasks", + metadata=dict(description="Tasks associated with the PluginFile resource."), + many=True, + ) class EntrypointPluginSchema(Schema): diff --git a/src/dioptra/restapi/v1/plugins/schema.py b/src/dioptra/restapi/v1/plugins/schema.py index fad908a7e..3a1d8029b 100644 --- a/src/dioptra/restapi/v1/plugins/schema.py +++ b/src/dioptra/restapi/v1/plugins/schema.py @@ -62,23 +62,6 @@ class PluginSnapshotRefSchema(PluginSnapshotRefBaseSchema): # type: ignore ) -PluginFileRefBaseSchema = generate_base_resource_ref_schema("PluginFile") - - -class PluginFileRefSchema(PluginFileRefBaseSchema): # type: ignore - """The reference schema for the data stored in a PluginFile.""" - - pluginId = fields.Int( - attribute="plugin_id", - data_key="plugin", - metadata=dict(description="ID for the Plugin resource this file belongs to."), - ) - filename = fields.String( - attribute="filename", - metadata=dict(description="Filename of the PluginFile resource."), - ) - - class PluginTaskParameterSchema(Schema): """The schema for the data stored in a PluginTaskParameter""" @@ -168,6 +151,29 @@ class PluginTaskSchema(Schema): ) +PluginFileRefBaseSchema = generate_base_resource_ref_schema("PluginFile") + + +class PluginFileRefSchema(PluginFileRefBaseSchema): # type: ignore + """The reference schema for the data stored in a PluginFile.""" + + pluginId = fields.Int( + attribute="plugin_id", + data_key="plugin", + metadata=dict(description="ID for the Plugin resource this file belongs to."), + ) + filename = fields.String( + attribute="filename", + metadata=dict(description="Filename of the PluginFile resource."), + ) + tasks = fields.Nested( + PluginTaskSchema, + attribute="tasks", + metadata=dict(description="Tasks associated with the PluginFile resource."), + many=True, + ) + + PluginBaseSchema = generate_base_resource_schema("Plugin", snapshot=True) diff --git a/src/dioptra/restapi/v1/plugins/service.py b/src/dioptra/restapi/v1/plugins/service.py index fc2976707..d537c19da 100644 --- a/src/dioptra/restapi/v1/plugins/service.py +++ b/src/dioptra/restapi/v1/plugins/service.py @@ -1206,7 +1206,7 @@ def delete(self, plugin_id: int, plugin_file_id: int, **kwargs) -> dict[str, Any plugin = plugin_file_dict["plugin"] - _update_plugin_and_file_snapshots(plugin, plugin_file) + _update_plugin_and_file_snapshots(plugin, plugin_file, delete=True) plugin_file_id_to_return = plugin_file.resource_id # to return to user db.session.add( @@ -1353,6 +1353,7 @@ def _update_plugin_and_file_snapshots( plugin_file: models.PluginFile, delete: bool = False, ) -> None: + # use this method when creating, modifying, or deleting plugin files. # add snapshot for plugin new_plugin = models.Plugin( @@ -1363,9 +1364,6 @@ def _update_plugin_and_file_snapshots( ) db.session.add(new_plugin) - if delete: - return - # add snapshot for existing plugin_plugin_files for plugin_plugin_file in plugin.plugin_plugin_files: if ( @@ -1378,6 +1376,9 @@ def _update_plugin_and_file_snapshots( ) db.session.add(existing_plugin_plugin_file) + if delete: + return + # add snapshot for new plugin_plugin_file new_plugin_plugin_file = models.PluginPluginFile( plugin=new_plugin, diff --git a/src/dioptra/restapi/v1/utils.py b/src/dioptra/restapi/v1/utils.py index 19dc96d7d..9b90155d6 100644 --- a/src/dioptra/restapi/v1/utils.py +++ b/src/dioptra/restapi/v1/utils.py @@ -271,6 +271,7 @@ def build_entrypoint_plugin(plugin_with_files: PluginWithFilesDict) -> dict[str, f"{PLUGINS}/{plugin.resource_id}/{PLUGIN_FILES}/{plugin_file.resource_id}/" f"snapshots/{plugin_file.resource_snapshot_id}" ), + "tasks": [build_plugin_task(task) for task in plugin_file.tasks] } for plugin_file in plugin_files ], @@ -314,6 +315,7 @@ def build_plugin_file_ref(plugin_file: models.PluginFile) -> dict[str, Any]: "url": build_url( f"{PLUGINS}/{plugin_id}/{PLUGIN_FILES}/{plugin_file.resource_id}" ), + "tasks": [build_plugin_task(task) for task in plugin_file.tasks] } diff --git a/src/frontend/src/dialogs/AssignPluginsDialog.vue b/src/frontend/src/dialogs/AssignPluginsDialog.vue index a1b11c75e..b090f2985 100644 --- a/src/frontend/src/dialogs/AssignPluginsDialog.vue +++ b/src/frontend/src/dialogs/AssignPluginsDialog.vue @@ -26,17 +26,42 @@
Plugins:
@@ -70,7 +95,7 @@ }) async function submitPlugins() { - let pluginsToAdd = [] + let pluginsToAdd = [...pluginsToUpdate.value] let pluginsToRemove = [] selectedPluginIds.value.forEach((plugin) => { @@ -86,39 +111,20 @@ }) try { - // Wait for all add and remove operations to finish - const addPromise = (pluginsToAdd.length > 0) ? addPlugins(pluginsToAdd) : Promise.resolve() - const removePromises = pluginsToRemove.map(plugin => removePlugin(plugin)) - await Promise.all([addPromise, ...removePromises]); - - // Log after both operations are complete - emit('refreshTable') + if(pluginsToAdd.length > 0) { + await api.addPluginsToEntrypoint(props.editObj.id, pluginsToAdd) + } + for(const plugin of pluginsToRemove) { + await api.removePluginFromEntrypoint(props.editObj.id, plugin) + } notify.success(`Successfully updated plugins for '${props.editObj.name}'`) showDialog.value = false + emit('refreshTable') } catch (err) { notify.error("Error in processing plugins: " + err.message); } } - - async function addPlugins(pluginsToAdd) { - try { - await api.addPluginsToEntrypoint(props.editObj.id, pluginsToAdd) - } catch(err) { - console.log('err = ', err) - notify.error(err.response.data.message); - } - } - - async function removePlugin(plugin) { - try { - await api.removePluginFromEntrypoint(props.editObj.id, plugin) - } catch(err) { - notify.error(err.response.data.message); - } - } - - async function getPlugins(val = '', update) { update(async () => { try { @@ -127,11 +133,23 @@ rowsPerPage: 0, // get all index: 0 }) - pluginOptions.value = res.data.data + pluginOptions.value = res.data.data.filter((plugin) => !selectedPluginIds.value.includes(plugin.id)) } catch(err) { notify.error(err.response.data.message) } }) } + const pluginsToUpdate = ref([]) + + async function syncPlugin(pluginID, index) { + try { + const res = await api.getItem('plugins', pluginID) + selectedPlugins.value.splice(index, 1, res.data) + pluginsToUpdate.value.push(pluginID) + } catch(err) { + console.warn(err) + } + } + \ No newline at end of file diff --git a/src/frontend/src/views/ArtifactsView.vue b/src/frontend/src/views/ArtifactsView.vue index 75a8a6934..a96d50164 100644 --- a/src/frontend/src/views/ArtifactsView.vue +++ b/src/frontend/src/views/ArtifactsView.vue @@ -16,14 +16,6 @@
{{ props.row.group.name }}
@@ -48,7 +40,6 @@ diff --git a/src/frontend/src/views/EntryPointsView.vue b/src/frontend/src/views/EntryPointsView.vue index 39d17c843..2290967cb 100644 --- a/src/frontend/src/views/EntryPointsView.vue +++ b/src/frontend/src/views/EntryPointsView.vue @@ -29,21 +29,6 @@ EMPTY - - - @@ -53,8 +38,17 @@ :key="i" color="secondary" text-color="white" + clickable + @click.stop="editEntrypoint = props.row; showAssignPluginsDialog = true" > {{ plugin.name }} + {{ props.row.group.name }} @@ -61,7 +87,6 @@