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

Add spinner variant #4869

Open
wants to merge 13 commits into
base: feat/fo-assistant
Choose a base branch
from
29 changes: 29 additions & 0 deletions app/packages/components/src/components/Loading/LoadingSpinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from "react";

import { CircularProgress } from "@mui/material";

const LoadingSpinner = ({
color = "base",
size = "medium",
}: {
color?: string;
size?: string;
}) => {
const COLORS: { [key: string]: string } = {
base: "#FFC59B",
primary: "primary",
secondary: "secondary",
error: "error",
warning: "warning",
info: "info",
success: "success",
};
const SIZES: { [key: string]: string } = {
small: "1rem",
medium: "2rem",
large: "3rem",
};
return <CircularProgress sx={{ color: COLORS[color] }} size={SIZES[size]} />;
};

export default LoadingSpinner;
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import LoadingDots from "@fiftyone/components/src/components/Loading/LoadingDots";
import LoadingSpinner from "@fiftyone/components/src/components/Loading/LoadingSpinner";
import { Box } from "@mui/material";
import React from "react";
import { getComponentProps } from "../utils";

export default function LoadingView(props) {
const { schema } = props;
const { view = {} } = schema;
const { label = "Loading" } = view;
const { text = "Loading", variant, color, size } = view;

return (
<Box {...getComponentProps(props, "container")}>
<LoadingDots text={label} {...getComponentProps(props, "loading")} />
{variant === "spinner" ? (
<LoadingSpinner color={color} size={size} />
) : (
<LoadingDots text={text} {...getComponentProps(props, "loading")} />
)}
</Box>
);
}
19 changes: 19 additions & 0 deletions fiftyone/factory/repo_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
DelegatedOperationRepo,
MongoDelegatedOperationRepo,
)
from fiftyone.factory.repos.execution_store import (
ExecutionStoreRepo,
MongoExecutionStoreRepo,
)

_db: Database = None

Expand Down Expand Up @@ -44,3 +48,18 @@ def delegated_operation_repo() -> DelegatedOperationRepo:
return RepositoryFactory.repos[
MongoDelegatedOperationRepo.COLLECTION_NAME
]

@staticmethod
def execution_store_repo() -> ExecutionStoreRepo:
"""Factory method for execution store repository."""
if (
MongoExecutionStoreRepo.COLLECTION_NAME
not in RepositoryFactory.repos
):
RepositoryFactory.repos[
MongoExecutionStoreRepo.COLLECTION_NAME
] = MongoExecutionStoreRepo(
collection=_get_db()[MongoExecutionStoreRepo.COLLECTION_NAME]
)

return RepositoryFactory.repos[MongoExecutionStoreRepo.COLLECTION_NAME]
128 changes: 128 additions & 0 deletions fiftyone/factory/repos/execution_store.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
"""
Execution store repository.
"""

import datetime
from pymongo.collection import Collection
from fiftyone.operators.store.models import StoreDocument, KeyDocument


def _where(store_name, key=None):
query = {"store_name": store_name}
if key is not None:
query["key"] = key
return query


class ExecutionStoreRepo:
"""Base class for execution store repositories."""

COLLECTION_NAME = "execution_store"

def __init__(self, collection: Collection):
self._collection = collection

def create_store(self, store_name, permissions=None) -> StoreDocument:
"""Creates a store in the execution store."""
store_doc = StoreDocument(
store_name=store_name, permissions=permissions
)
self._collection.insert_one(store_doc.dict())
return store_doc

def list_stores(self) -> list[str]:
"""Lists all stores in the execution store."""
# ensure that only store_name is returned, and only unique values
return self._collection.distinct("store_name")

def set_key(self, store_name, key, value, ttl=None) -> KeyDocument:
"""Sets or updates a key in the specified store"""
now = datetime.datetime.now()
expiration = KeyDocument.get_expiration(ttl)
key_doc = KeyDocument(
store_name=store_name, key=key, value=value, updated_at=now
)

# Prepare the update operations
update_fields = {
"$set": key_doc.dict(
exclude={"created_at", "expires_at", "store_name", "key"}
),
"$setOnInsert": {
"store_name": store_name,
"key": key,
"created_at": now,
"expires_at": expiration if ttl else None,
},
}

# Perform the upsert operation
result = self._collection.update_one(
_where(store_name, key), update_fields, upsert=True
)

if result.upserted_id:
key_doc.created_at = now
else:
key_doc.updated_at = now

return key_doc

def get_key(self, store_name, key) -> KeyDocument:
"""Gets a key from the specified store."""
raw_key_doc = self._collection.find_one(_where(store_name, key))
key_doc = KeyDocument(**raw_key_doc) if raw_key_doc else None
return key_doc

def list_keys(self, store_name) -> list[str]:
"""Lists all keys in the specified store."""
keys = self._collection.find(_where(store_name), {"key": 1})
return [key["key"] for key in keys]

def update_ttl(self, store_name, key, ttl) -> bool:
"""Updates the TTL for a key."""
expiration = KeyDocument.get_expiration(ttl)
result = self._collection.update_one(
_where(store_name, key), {"$set": {"expires_at": expiration}}
)
return result.modified_count > 0

def delete_key(self, store_name, key) -> bool:
"""Deletes the document that matches the store name and key."""
result = self._collection.delete_one(_where(store_name, key))
return result.deleted_count > 0

def delete_store(self, store_name) -> int:
"""Deletes the entire store."""
result = self._collection.delete_many(_where(store_name))
return result.deleted_count


class MongoExecutionStoreRepo(ExecutionStoreRepo):
"""MongoDB implementation of execution store repository."""

COLLECTION_NAME = "execution_store"

def __init__(self, collection: Collection):
super().__init__(collection)
self._create_indexes()

def _create_indexes(self):
indices = self._collection.list_indexes()
expires_at_name = "expires_at"
store_name_name = "store_name"
key_name = "key"
full_key_name = "store_name_and_key"
if expires_at_name not in indices:
self._collection.create_index(
expires_at_name, name=expires_at_name, expireAfterSeconds=0
)
if full_key_name not in indices:
self._collection.create_index(
[(store_name_name, 1), (key_name, 1)],
name=full_key_name,
unique=True,
)
for name in [store_name_name, key_name]:
if name not in indices:
self._collection.create_index(name, name=name)
14 changes: 14 additions & 0 deletions fiftyone/operators/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,20 @@ def set_progress(self, progress=None, label=None):
else:
self.log(f"Progress: {progress} - {label}")

# TODO resolve circular import so this can have a type
def create_store(self, store_name):
"""Creates a new store with the specified name.

Args:
store_name: the name of the store

Returns:
a :class:`fiftyone.operators.store.ExecutionStore`
"""
from fiftyone.operators.store import ExecutionStore

return ExecutionStore.create(store_name)

def serialize(self):
"""Serializes the execution context.

Expand Down
18 changes: 18 additions & 0 deletions fiftyone/operators/store/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""
FiftyOne execution store module.

| Copyright 2017-2024, Voxel51, Inc.
| `voxel51.com <https://voxel51.com/>`_
|
"""

from .service import ExecutionStoreService
from .store import ExecutionStore
from .models import StoreDocument, KeyDocument

__all__ = [
"ExecutionStoreService",
"StoreDocument",
"KeyDocument",
"ExecutionStore",
]
35 changes: 35 additions & 0 deletions fiftyone/operators/store/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""
Store and key models for the execution store.
"""

from pydantic import BaseModel, Field
from typing import Optional, Dict, Any
import datetime


class KeyDocument(BaseModel):
"""Model representing a key in the store."""

store_name: str
key: str
value: Any
created_at: datetime.datetime = Field(
default_factory=datetime.datetime.now
)
updated_at: Optional[datetime.datetime] = None
expires_at: Optional[datetime.datetime] = None

@staticmethod
def get_expiration(ttl: Optional[int]) -> Optional[datetime.datetime]:
"""Gets the expiration date for a key with the given TTL."""
if ttl is None:
return None

return datetime.datetime.now() + datetime.timedelta(seconds=ttl)


class StoreDocument(KeyDocument):
"""Model representing a Store."""

key: str = "__store__"
value: Optional[Dict[str, Any]] = None
Loading
Loading