diff --git a/label_studio/io_storages/redis/form_layout.yml b/label_studio/io_storages/redis/form_layout.yml index 6a4efbb7bd09..1486988081cd 100644 --- a/label_studio/io_storages/redis/form_layout.yml +++ b/label_studio/io_storages/redis/form_layout.yml @@ -58,5 +58,7 @@ ImportStorage: label: "Tasks - Treat each JSON or JSONL file as a task definition (one or more tasks per file)" ExportStorage: + - columnCount: 1 + fields: *redis_title - columnCount: 2 fields: *redis_params diff --git a/label_studio/io_storages/redis/models.py b/label_studio/io_storages/redis/models.py index 5a862919ca48..36449725aa23 100644 --- a/label_studio/io_storages/redis/models.py +++ b/label_studio/io_storages/redis/models.py @@ -85,7 +85,7 @@ def can_resolve_url(self, url): def iter_objects(self): client = self.get_client() - path = str(self.path) + path = str(self.path or "") for key in client.keys(path + '*'): yield key @@ -135,8 +135,10 @@ def save_annotation(self, annotation): logger.debug(f'Creating new object on {self.__class__.__name__} Storage {self} for annotation {annotation}') ser_annotation = self._get_serialized_data(annotation) - # get key that identifies this object in storage + # get key that identifies this object in storage, prepend path prefix if set key = RedisExportStorageLink.get_key(annotation) + if self.path: + key = str(self.path).rstrip('/') + '/' + key # put object into storage client.set(key, json.dumps(ser_annotation)) diff --git a/web/apps/labelstudio/src/pages/Settings/StorageSettings/providers/redis.ts b/web/apps/labelstudio/src/pages/Settings/StorageSettings/providers/redis.ts index 327f2a5fd5af..329c796a3a8a 100644 --- a/web/apps/labelstudio/src/pages/Settings/StorageSettings/providers/redis.ts +++ b/web/apps/labelstudio/src/pages/Settings/StorageSettings/providers/redis.ts @@ -39,15 +39,14 @@ export const redisProvider: ProviderConfig = { schema: z.string().default("6379"), }, { - name: "prefix", + name: "path", type: "text", label: "Bucket prefix", placeholder: "path/to/files", schema: z.string().optional().default(""), - target: "export", }, ], - layout: [{ fields: ["host", "port", "db", "password"] }, { fields: ["prefix"] }], + layout: [{ fields: ["host", "port", "db", "password"] }, { fields: ["path"] }], }; export default redisProvider; diff --git a/web/libs/datamanager/src/stores/AppStore.js b/web/libs/datamanager/src/stores/AppStore.js index 1f9b5a8f2030..655c78508874 100644 --- a/web/libs/datamanager/src/stores/AppStore.js +++ b/web/libs/datamanager/src/stores/AppStore.js @@ -576,13 +576,13 @@ export const AppStore = types self.viewsStore.fetchColumns(); - const requests = [self.fetchProject()]; - - // Only fetch all users if not disabled globally + // Fetch users before tabs so annotator/reviewer references in task data can be resolved. if (!isFF(FF_DISABLE_GLOBAL_USER_FETCHING)) { - requests.push(self.fetchUsers()); + yield self.fetchUsers(); } + const requests = [self.fetchProject()]; + if (!isLabelStream || (self.project?.show_annotation_history && task)) { if (self.SDK.settings?.onlyVirtualTabs && self.project?.show_annotation_history && !task) { requests.push( diff --git a/web/libs/datamanager/src/stores/Assignee.js b/web/libs/datamanager/src/stores/Assignee.js index d34f6ad8586a..c620f0a0715b 100644 --- a/web/libs/datamanager/src/stores/Assignee.js +++ b/web/libs/datamanager/src/stores/Assignee.js @@ -3,6 +3,10 @@ import { User } from "./Users"; import { StringOrNumberID } from "./types"; import { FF_DISABLE_GLOBAL_USER_FETCHING, isFF } from "../utils/feature-flags"; +// Use safeReference so that unresolved user IDs (race condition during initial load) +// return undefined instead of throwing an MST error. +const userSafeReference = types.safeReference(User); + // Create a union type that can handle both user references and direct user objects const UserOrReference = types.union({ dispatcher: (snapshot) => { @@ -11,11 +15,11 @@ const UserOrReference = types.union({ return User; } // Otherwise, it's a reference to a user ID - return types.reference(User); + return userSafeReference; }, cases: { [User.name]: User, - reference: types.reference(User), + reference: userSafeReference, }, }); @@ -29,28 +33,28 @@ export const Assignee = types }) .views((self) => ({ get firstName() { - return self.user.firstName; + return self.user?.firstName ?? ""; }, get lastName() { - return self.user.lastName; + return self.user?.lastName ?? ""; }, get username() { - return self.user.username; + return self.user?.username ?? ""; }, get email() { - return self.user.email; + return self.user?.email ?? ""; }, get lastActivity() { - return self.user.lastActivity; + return self.user?.lastActivity ?? ""; }, get avatar() { - return self.user.avatar; + return self.user?.avatar ?? null; }, get initials() { - return self.user.initials; + return self.user?.initials ?? ""; }, get fullName() { - return self.user.fullName; + return self.user?.fullName ?? ""; }, })) .preProcessSnapshot((sn) => {