Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove the externalintegrationslinks table #1379

Merged
merged 3 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""Remove ExternalIntegrationLink.

Revision ID: 5d71a80073d5
Revises: 1c566151741f
Create Date: 2023-09-13 15:23:07.566404+00:00

"""
import sqlalchemy as sa

from alembic import op

# revision identifiers, used by Alembic.
revision = "5d71a80073d5"
down_revision = "1c566151741f"
branch_labels = None
depends_on = None


def upgrade() -> None:
op.drop_index(
"ix_externalintegrationslinks_external_integration_id",
table_name="externalintegrationslinks",
)
op.drop_index(
"ix_externalintegrationslinks_library_id",
table_name="externalintegrationslinks",
)
op.drop_index(
"ix_externalintegrationslinks_other_integration_id",
table_name="externalintegrationslinks",
)
op.drop_index(
"ix_externalintegrationslinks_purpose", table_name="externalintegrationslinks"
)
op.drop_table("externalintegrationslinks")


def downgrade() -> None:
op.create_table(
"externalintegrationslinks",
sa.Column("id", sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column(
"external_integration_id", sa.INTEGER(), autoincrement=False, nullable=True
),
sa.Column("library_id", sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column(
"other_integration_id", sa.INTEGER(), autoincrement=False, nullable=True
),
sa.Column("purpose", sa.VARCHAR(), autoincrement=False, nullable=True),
sa.ForeignKeyConstraint(
["external_integration_id"],
["externalintegrations.id"],
name="externalintegrationslinks_external_integration_id_fkey",
),
sa.ForeignKeyConstraint(
["library_id"],
["libraries.id"],
name="externalintegrationslinks_library_id_fkey",
),
sa.ForeignKeyConstraint(
["other_integration_id"],
["externalintegrations.id"],
name="externalintegrationslinks_other_integration_id_fkey",
),
sa.PrimaryKeyConstraint("id", name="externalintegrationslinks_pkey"),
)
op.create_index(
"ix_externalintegrationslinks_purpose",
"externalintegrationslinks",
["purpose"],
unique=False,
)
op.create_index(
"ix_externalintegrationslinks_other_integration_id",
"externalintegrationslinks",
["other_integration_id"],
unique=False,
)
op.create_index(
"ix_externalintegrationslinks_library_id",
"externalintegrationslinks",
["library_id"],
unique=False,
)
op.create_index(
"ix_externalintegrationslinks_external_integration_id",
"externalintegrationslinks",
["external_integration_id"],
unique=False,
)
91 changes: 7 additions & 84 deletions api/admin/controller/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
INTEGRATION_NAME_ALREADY_IN_USE,
INTEGRATION_URL_ALREADY_IN_USE,
INVALID_CONFIGURATION_OPTION,
MISSING_INTEGRATION,
MISSING_SERVICE,
NO_PROTOCOL_FOR_NEW_SERVICE,
NO_SUCH_LIBRARY,
Expand All @@ -37,7 +36,6 @@
from core.model import (
ConfigurationSetting,
ExternalIntegration,
ExternalIntegrationLink,
IntegrationConfiguration,
IntegrationLibraryConfiguration,
Library,
Expand All @@ -58,66 +56,6 @@

NO_MIRROR_INTEGRATION = "NO_MIRROR"

def _set_storage_external_integration_link(
self, service: ExternalIntegration, purpose: str, setting_key: str
) -> Optional[ProblemDetail]:
"""Either set or delete the external integration link between the
service and the storage integration.

:param service: Service's ExternalIntegration object

:param purpose: Service's purpose

:param setting_key: Key of the configuration setting that must be set in the storage integration.
For example, a specific bucket (MARC, Analytics, etc.).

:return: ProblemDetail object if the operation failed
"""
mirror_integration_id = flask.request.form.get("mirror_integration_id")

if not mirror_integration_id:
return None

# If no storage integration was selected, then delete the existing
# external integration link.
if mirror_integration_id == self.NO_MIRROR_INTEGRATION:
current_integration_link = get_one(
self._db,
ExternalIntegrationLink,
library_id=None,
external_integration_id=service.id,
purpose=purpose,
)

if current_integration_link:
self._db.delete(current_integration_link)
else:
storage_integration = get_one(
self._db, ExternalIntegration, id=mirror_integration_id
)

# Only get storage integrations that have a specific configuration setting set.
# For example: a specific bucket.
if (
not storage_integration
or not storage_integration.setting(setting_key).value
):
return MISSING_INTEGRATION

current_integration_link_created, ignore = get_one_or_create(
self._db,
ExternalIntegrationLink,
library_id=None,
external_integration_id=service.id,
purpose=purpose,
)

current_integration_link_created.other_integration_id = (
storage_integration.id
)

return None

def _get_settings_class(
self, registry: IntegrationRegistry, protocol_name: str, is_child=False
) -> Type[BaseSettings] | ProblemDetail | None:
Expand Down Expand Up @@ -233,29 +171,14 @@
settings = dict()
for setting in protocol.get("settings", []):
key = setting.get("key")

# If the setting is a covers or books mirror, we need to get
# the value from ExternalIntegrationLink and
# not from a ConfigurationSetting.
if key.endswith("mirror_integration_id"):
storage_integration = get_one(
self._db,
ExternalIntegrationLink,
external_integration_id=service.id,
)
if storage_integration:
value = str(storage_integration.other_integration_id)
else:
value = self.NO_MIRROR_INTEGRATION
if setting.get("type") in ("list", "menu"):
value = ConfigurationSetting.for_externalintegration(

Check warning on line 175 in api/admin/controller/settings.py

View check run for this annotation

Codecov / codecov/patch

api/admin/controller/settings.py#L175

Added line #L175 was not covered by tests
key, service
).json_value
else:
if setting.get("type") in ("list", "menu"):
value = ConfigurationSetting.for_externalintegration(
key, service
).json_value
else:
value = ConfigurationSetting.for_externalintegration(
key, service
).value
value = ConfigurationSetting.for_externalintegration(
key, service
).value
settings[key] = value

service_info = dict(
Expand Down
6 changes: 1 addition & 5 deletions core/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -537,11 +537,7 @@ def _bulk_operation(self):
CollectionMissing,
collections_identifiers,
)
from core.model.configuration import (
ConfigurationSetting,
ExternalIntegration,
ExternalIntegrationLink,
)
from core.model.configuration import ConfigurationSetting, ExternalIntegration
from core.model.contributor import Contribution, Contributor
from core.model.coverage import (
BaseCoverageRecord,
Expand Down
8 changes: 0 additions & 8 deletions core/model/collection.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Collection, CollectionIdentifier, CollectionMissing
from __future__ import annotations

import logging
from abc import ABCMeta, abstractmethod
from typing import TYPE_CHECKING, List, Optional

Expand Down Expand Up @@ -946,13 +945,6 @@ def delete(self, search_index=None):
# Delete the ExternalIntegration associated with this
# Collection, assuming it wasn't deleted already.
if self.external_integration:
for link in self.external_integration.links:
if link.other_integration and link.other_integration.goal == "storage":
logging.info(
f"Deletion of collection {self.name} is disassociating "
f"storage integration {link.other_integration.name}."
)

_db.delete(self.external_integration)

# Now delete the Collection itself.
Expand Down
80 changes: 0 additions & 80 deletions core/model/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,38 +26,6 @@
from core.model import Collection # noqa: autoflake


class ExternalIntegrationLink(Base):
__tablename__ = "externalintegrationslinks"

NO_MIRROR_INTEGRATION = "NO_MIRROR"
# Possible purposes that a storage external integration can be used for.
# These string literals may be stored in the database, so changes to them
# may need to be accompanied by a DB migration.
COVERS = "covers_mirror"
COVERS_KEY = f"{COVERS}_integration_id"

OPEN_ACCESS_BOOKS = "books_mirror"
OPEN_ACCESS_BOOKS_KEY = f"{OPEN_ACCESS_BOOKS}_integration_id"

PROTECTED_ACCESS_BOOKS = "protected_access_books_mirror"
PROTECTED_ACCESS_BOOKS_KEY = f"{PROTECTED_ACCESS_BOOKS}_integration_id"

ANALYTICS = "analytics_mirror"
ANALYTICS_KEY = f"{ANALYTICS}_integration_id"

MARC = "MARC_mirror"

id = Column(Integer, primary_key=True)
external_integration_id = Column(
Integer, ForeignKey("externalintegrations.id"), index=True
)
library_id = Column(Integer, ForeignKey("libraries.id"), index=True)
other_integration_id = Column(
Integer, ForeignKey("externalintegrations.id"), index=True
)
purpose = Column(Unicode, index=True)


class ExternalIntegration(Base):

"""An external integration contains configuration for connecting
Expand Down Expand Up @@ -224,20 +192,6 @@ class ExternalIntegration(Base):
foreign_keys="Collection.external_integration_id",
)

links: Mapped[List[ExternalIntegrationLink]] = relationship(
"ExternalIntegrationLink",
backref="integration",
foreign_keys="ExternalIntegrationLink.external_integration_id",
cascade="all, delete-orphan",
)

other_links: Mapped[List[ExternalIntegrationLink]] = relationship(
"ExternalIntegrationLink",
backref="other_integration",
foreign_keys="ExternalIntegrationLink.other_integration_id",
cascade="all, delete-orphan",
)

libraries: Mapped[List[Library]] = relationship(
"Library",
back_populates="integrations",
Expand All @@ -260,40 +214,6 @@ def for_goal(cls, _db, goal):

return integrations

@classmethod
def for_collection_and_purpose(cls, _db, collection, purpose):
"""Find the ExternalIntegration for the collection.

:param collection: Use the mirror configuration for this Collection.
:param purpose: Use the purpose of the mirror configuration.
"""
qu = (
_db.query(cls)
.join(
ExternalIntegrationLink,
ExternalIntegrationLink.other_integration_id == cls.id,
)
.filter(
ExternalIntegrationLink.external_integration_id
== collection.external_integration_id,
ExternalIntegrationLink.purpose == purpose,
)
)
integrations = qu.all()
if not integrations:
raise CannotLoadConfiguration(
"No storage integration for collection '%s' and purpose '%s' is configured."
% (collection.name, purpose)
)
if len(integrations) > 1:
raise CannotLoadConfiguration(
"Multiple integrations found for collection '%s' and purpose '%s'"
% (collection.name, purpose)
)

[integration] = integrations
return integration

@classmethod
def lookup(cls, _db, protocol, goal, library=None):
integrations = _db.query(cls).filter(cls.protocol == protocol, cls.goal == goal)
Expand Down
Loading