diff --git a/.changeset/mock-api-layer.md b/.changeset/mock-api-layer.md new file mode 100644 index 00000000..b021d606 --- /dev/null +++ b/.changeset/mock-api-layer.md @@ -0,0 +1,13 @@ +--- +"@elixir-cloud/cloud-registry": minor +"@elixir-cloud/design": minor +"@elixir-cloud/drs": minor +"@elixir-cloud/drs-filer": minor +"@elixir-cloud/service-registry": minor +"@elixir-cloud/tes": minor +"@elixir-cloud/trs": minor +"@elixir-cloud/trs-filer": minor +"@elixir-cloud/wes": minor +--- + +feat: complete mock API layer implementation diff --git a/.gitignore b/.gitignore index 7bbf0bde..510ddaad 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,6 @@ yarn-error.log* # vercel .vercel -.idea \ No newline at end of file +.idea + +apps/documentation/public/mocks diff --git a/apps/documentation/pages/docs/_meta.json b/apps/documentation/pages/docs/_meta.json index 74136bbc..a2326125 100644 --- a/apps/documentation/pages/docs/_meta.json +++ b/apps/documentation/pages/docs/_meta.json @@ -2,6 +2,7 @@ "introduction": "Introduction", "installation": "Installation", "customization": "Customization", + "mocking": "Mock APIs", "providers": "Providers", "-- Supported Services": { "type": "separator", diff --git a/apps/documentation/pages/docs/mocking.mdx b/apps/documentation/pages/docs/mocking.mdx new file mode 100644 index 00000000..6e481fc4 --- /dev/null +++ b/apps/documentation/pages/docs/mocking.mdx @@ -0,0 +1,32 @@ +# Mock APIs + +ELIXIR Cloud Components provides a built-in mock API layer to allow reliable testing, offline development, and demonstration without relying on live services. + +Our documentation and demo pages are configured to use these mock responses to ensure consistent behavior. + +## Enabling Mock APIs + +The mock API layer is controlled via environment variables. By default, components will attempt to use live API routes provided via the `baseUrl` property. If you want to intercept these requests and serve mock data instead, set the appropriate environment variable: + +- **For Vite / standard web environments:** `VITE_USE_MOCK_API=true` +- **For Next.js environments:** `NEXT_PUBLIC_USE_MOCK_API=true` + +Once enabled, API calls (e.g., fetching a GA4GH WES run or an ELIXIR Service Registry listing) will automatically be intercepted and resolved using local JSON files. + +## How it works + +The components use a centralized `fetcher` utility from `@elixir-cloud/design` under the hood. When the mock environment variable is detected, `fetcher` substitutes the HTTP request with a local fetch to the `/mocks/` directory. + +> **Note:** If a mock file doesn't exist for a particular endpoint, the library will gracefully fall back to the live API to ensure development isn't blocked. + +## Writing or Updating Mocks + +Mock files are standard JSON files stored in the `mocks/` directory at the root of the repository. When setting up a new application or documentation site, ensure that this folder is publicly served (e.g., via a symlink to `public/mocks/` in Next.js). + +If you want to modify a mock response, simply edit the corresponding JSON file. For example, to change the mocked response of a specific TRS tool, you would modify: + +```text +/mocks/ga4gh-trs/tools/id.json +``` + +No rebuild of the components is necessary when updating mock JSON files—they are resolved at runtime by the browser. diff --git a/apps/ssr-test/next-env.d.ts b/apps/ssr-test/next-env.d.ts new file mode 100644 index 00000000..9edff1c7 --- /dev/null +++ b/apps/ssr-test/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +import "./.next/types/routes.d.ts"; + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/mocks/elixir-cloud-registry/service-info-post.json b/mocks/elixir-cloud-registry/service-info-post.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/mocks/elixir-cloud-registry/service-info-post.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/mocks/elixir-cloud-registry/service-info.json b/mocks/elixir-cloud-registry/service-info.json new file mode 100644 index 00000000..066b410a --- /dev/null +++ b/mocks/elixir-cloud-registry/service-info.json @@ -0,0 +1,14 @@ +{ + "id": "mock-registry-info-001", + "name": "Mock ELIXIR Registry Info", + "type": { + "group": "org.elixir", + "artifact": "registry-info", + "version": "1.0.0" + }, + "organization": { + "name": "ELIXIR Mock", + "url": "https://example.com" + }, + "version": "1.0.0" +} \ No newline at end of file diff --git a/mocks/elixir-cloud-registry/services/delete.json b/mocks/elixir-cloud-registry/services/delete.json new file mode 100644 index 00000000..ff99dcf1 --- /dev/null +++ b/mocks/elixir-cloud-registry/services/delete.json @@ -0,0 +1,3 @@ +{ + "id": "mock-elixir-cr-deleted" +} \ No newline at end of file diff --git a/mocks/elixir-cloud-registry/services/get.json b/mocks/elixir-cloud-registry/services/get.json new file mode 100644 index 00000000..3e8bf12c --- /dev/null +++ b/mocks/elixir-cloud-registry/services/get.json @@ -0,0 +1,17 @@ +[ + { + "id": "mock-elixir-cr-001", + "name": "Mock ELIXIR Cloud Registry Service", + "type": { + "group": "org.elixir", + "artifact": "cloud-registry", + "version": "1.0.0" + }, + "organization": { + "name": "ELIXIR Mock", + "url": "https://example.com" + }, + "version": "1.0.0", + "url": "https://example.com/cr/v1" + } +] \ No newline at end of file diff --git a/mocks/elixir-cloud-registry/services/id.json b/mocks/elixir-cloud-registry/services/id.json new file mode 100644 index 00000000..8b23a2f6 --- /dev/null +++ b/mocks/elixir-cloud-registry/services/id.json @@ -0,0 +1,15 @@ +{ + "id": "mock-elixir-cr-001", + "name": "Mock ELIXIR Cloud Registry Service", + "type": { + "group": "org.elixir", + "artifact": "cloud-registry", + "version": "1.0.0" + }, + "organization": { + "name": "ELIXIR Mock", + "url": "https://example.com" + }, + "version": "1.0.0", + "url": "https://example.com/cr/v1" +} \ No newline at end of file diff --git a/mocks/elixir-cloud-registry/services/post.json b/mocks/elixir-cloud-registry/services/post.json new file mode 100644 index 00000000..c66969d9 --- /dev/null +++ b/mocks/elixir-cloud-registry/services/post.json @@ -0,0 +1,3 @@ +{ + "id": "mock-elixir-cr-new-post" +} \ No newline at end of file diff --git a/mocks/elixir-cloud-registry/services/put.json b/mocks/elixir-cloud-registry/services/put.json new file mode 100644 index 00000000..c290ab53 --- /dev/null +++ b/mocks/elixir-cloud-registry/services/put.json @@ -0,0 +1,3 @@ +{ + "id": "mock-elixir-cr-put" +} \ No newline at end of file diff --git a/mocks/elixir-cloud-registry/services/types.json b/mocks/elixir-cloud-registry/services/types.json new file mode 100644 index 00000000..f8e29965 --- /dev/null +++ b/mocks/elixir-cloud-registry/services/types.json @@ -0,0 +1,7 @@ +[ + { + "group": "org.elixir", + "artifact": "cloud-registry", + "version": "1.0.0" + } +] \ No newline at end of file diff --git a/mocks/elixir-drs-filer/objects/access.json b/mocks/elixir-drs-filer/objects/access.json new file mode 100644 index 00000000..f254be91 --- /dev/null +++ b/mocks/elixir-drs-filer/objects/access.json @@ -0,0 +1,4 @@ +{ + "url": "https://example.com/mock-drs-file.txt", + "headers": [] +} \ No newline at end of file diff --git a/mocks/elixir-drs-filer/objects/access/delete.json b/mocks/elixir-drs-filer/objects/access/delete.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/mocks/elixir-drs-filer/objects/access/delete.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/mocks/elixir-drs-filer/objects/delete.json b/mocks/elixir-drs-filer/objects/delete.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/mocks/elixir-drs-filer/objects/delete.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/mocks/elixir-drs-filer/objects/get.json b/mocks/elixir-drs-filer/objects/get.json new file mode 100644 index 00000000..b3521775 --- /dev/null +++ b/mocks/elixir-drs-filer/objects/get.json @@ -0,0 +1,24 @@ +{ + "objects": [ + { + "id": "mock-drs-001", + "name": "Mock DRS Object 1", + "size": 1024, + "created_time": "2023-01-01T00:00:00Z", + "updated_time": "2023-01-01T00:00:00Z", + "version": "1.0", + "mime_type": "text/plain", + "checksums": [ + { + "type": "md5", + "checksum": "d41d8cd98f00b204e9800998ecf8427e" + } + ] + } + ], + "pagination": { + "offset": 0, + "limit": 10, + "total": 1 + } +} \ No newline at end of file diff --git a/mocks/elixir-drs-filer/objects/id.json b/mocks/elixir-drs-filer/objects/id.json new file mode 100644 index 00000000..0925334c --- /dev/null +++ b/mocks/elixir-drs-filer/objects/id.json @@ -0,0 +1,22 @@ +{ + "id": "mock-drs-001", + "name": "Mock DRS Object 1", + "size": 1024, + "created_time": "2023-01-01T00:00:00Z", + "updated_time": "2023-01-01T00:00:00Z", + "version": "1.0", + "mime_type": "text/plain", + "checksums": [ + { + "type": "md5", + "checksum": "d41d8cd98f00b204e9800998ecf8427e" + } + ], + "access_methods": [ + { + "access_id": "access-001", + "type": "s3", + "region": "us-east-1" + } + ] +} \ No newline at end of file diff --git a/mocks/elixir-drs-filer/objects/post.json b/mocks/elixir-drs-filer/objects/post.json new file mode 100644 index 00000000..86bcacf1 --- /dev/null +++ b/mocks/elixir-drs-filer/objects/post.json @@ -0,0 +1,3 @@ +{ + "id": "mock-drs-newly-created" +} \ No newline at end of file diff --git a/mocks/elixir-drs-filer/objects/put.json b/mocks/elixir-drs-filer/objects/put.json new file mode 100644 index 00000000..e60491f8 --- /dev/null +++ b/mocks/elixir-drs-filer/objects/put.json @@ -0,0 +1,3 @@ +{ + "id": "mock-drs-updated" +} \ No newline at end of file diff --git a/mocks/elixir-drs-filer/service-info-post.json b/mocks/elixir-drs-filer/service-info-post.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/mocks/elixir-drs-filer/service-info-post.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/mocks/elixir-drs-filer/service-info.json b/mocks/elixir-drs-filer/service-info.json new file mode 100644 index 00000000..5af24d15 --- /dev/null +++ b/mocks/elixir-drs-filer/service-info.json @@ -0,0 +1,14 @@ +{ + "id": "mock-drs-service", + "name": "Mock DRS Service", + "type": { + "group": "org.ga4gh", + "artifact": "drs", + "version": "1.1.0" + }, + "organization": { + "name": "Mock Org", + "url": "https://example.com" + }, + "version": "1.1.0" +} \ No newline at end of file diff --git a/mocks/elixir-trs-filer/toolClasses/get.json b/mocks/elixir-trs-filer/toolClasses/get.json new file mode 100644 index 00000000..14251aef --- /dev/null +++ b/mocks/elixir-trs-filer/toolClasses/get.json @@ -0,0 +1 @@ +[{"id": "mock-class", "name": "Mock Class", "description": "Mock class description"}] \ No newline at end of file diff --git a/mocks/elixir-trs-filer/toolClasses/post.json b/mocks/elixir-trs-filer/toolClasses/post.json new file mode 100644 index 00000000..32771ef0 --- /dev/null +++ b/mocks/elixir-trs-filer/toolClasses/post.json @@ -0,0 +1 @@ +{"id": "mock-toolclass-post"} \ No newline at end of file diff --git a/mocks/elixir-trs-filer/toolClasses/put.json b/mocks/elixir-trs-filer/toolClasses/put.json new file mode 100644 index 00000000..84ed6dc2 --- /dev/null +++ b/mocks/elixir-trs-filer/toolClasses/put.json @@ -0,0 +1 @@ +{"id": "mock-toolclass-put"} \ No newline at end of file diff --git a/mocks/elixir-trs-filer/tools/containerfile.json b/mocks/elixir-trs-filer/tools/containerfile.json new file mode 100644 index 00000000..f67fd713 --- /dev/null +++ b/mocks/elixir-trs-filer/tools/containerfile.json @@ -0,0 +1 @@ +[{"content": "FROM ubuntu:latest\nRUN echo mock", "url": "https://example.com/containerfile"}] \ No newline at end of file diff --git a/mocks/elixir-trs-filer/tools/descriptor-path.json b/mocks/elixir-trs-filer/tools/descriptor-path.json new file mode 100644 index 00000000..8a05deb8 --- /dev/null +++ b/mocks/elixir-trs-filer/tools/descriptor-path.json @@ -0,0 +1 @@ +{"content": "mock descriptor by path", "url": "https://example.com/descriptor"} \ No newline at end of file diff --git a/mocks/elixir-trs-filer/tools/descriptor.json b/mocks/elixir-trs-filer/tools/descriptor.json new file mode 100644 index 00000000..55aac2d0 --- /dev/null +++ b/mocks/elixir-trs-filer/tools/descriptor.json @@ -0,0 +1 @@ +{"content": "mock descriptor content", "url": "https://example.com/descriptor"} \ No newline at end of file diff --git a/mocks/elixir-trs-filer/tools/files.json b/mocks/elixir-trs-filer/tools/files.json new file mode 100644 index 00000000..4850643d --- /dev/null +++ b/mocks/elixir-trs-filer/tools/files.json @@ -0,0 +1 @@ +[{"path": "/mock/file.txt", "file_type": "TEST_FILE"}] \ No newline at end of file diff --git a/mocks/elixir-trs-filer/tools/get.json b/mocks/elixir-trs-filer/tools/get.json new file mode 100644 index 00000000..95db1bb0 --- /dev/null +++ b/mocks/elixir-trs-filer/tools/get.json @@ -0,0 +1 @@ +[{"id": "mock-tool-001", "name": "Mock Tool", "organization": "Mock Org"}] \ No newline at end of file diff --git a/mocks/elixir-trs-filer/tools/id.json b/mocks/elixir-trs-filer/tools/id.json new file mode 100644 index 00000000..6fccdf04 --- /dev/null +++ b/mocks/elixir-trs-filer/tools/id.json @@ -0,0 +1 @@ +{"id": "mock-tool-001", "name": "Mock Tool", "organization": "Mock Org", "description": "A mocked tool"} \ No newline at end of file diff --git a/mocks/elixir-trs-filer/tools/post.json b/mocks/elixir-trs-filer/tools/post.json new file mode 100644 index 00000000..8517b16d --- /dev/null +++ b/mocks/elixir-trs-filer/tools/post.json @@ -0,0 +1 @@ +{"id": "mock-tool-new-post"} \ No newline at end of file diff --git a/mocks/elixir-trs-filer/tools/put.json b/mocks/elixir-trs-filer/tools/put.json new file mode 100644 index 00000000..d01346d9 --- /dev/null +++ b/mocks/elixir-trs-filer/tools/put.json @@ -0,0 +1 @@ +{"id": "mock-tool-put-updated"} \ No newline at end of file diff --git a/mocks/elixir-trs-filer/tools/tests.json b/mocks/elixir-trs-filer/tools/tests.json new file mode 100644 index 00000000..3fc16903 --- /dev/null +++ b/mocks/elixir-trs-filer/tools/tests.json @@ -0,0 +1 @@ +[{"content": "mock test content", "url": "https://example.com/test"}] \ No newline at end of file diff --git a/mocks/elixir-trs-filer/tools/version-id.json b/mocks/elixir-trs-filer/tools/version-id.json new file mode 100644 index 00000000..feae7634 --- /dev/null +++ b/mocks/elixir-trs-filer/tools/version-id.json @@ -0,0 +1 @@ +{"id": "v1", "name": "Version 1.0", "author": ["Mock Author"], "is_production": true} \ No newline at end of file diff --git a/mocks/elixir-trs-filer/tools/versions-post.json b/mocks/elixir-trs-filer/tools/versions-post.json new file mode 100644 index 00000000..2924a52c --- /dev/null +++ b/mocks/elixir-trs-filer/tools/versions-post.json @@ -0,0 +1 @@ +{"id": "mock-version-post"} \ No newline at end of file diff --git a/mocks/elixir-trs-filer/tools/versions-put.json b/mocks/elixir-trs-filer/tools/versions-put.json new file mode 100644 index 00000000..e1558ed3 --- /dev/null +++ b/mocks/elixir-trs-filer/tools/versions-put.json @@ -0,0 +1 @@ +{"id": "mock-version-put"} \ No newline at end of file diff --git a/mocks/elixir-trs-filer/tools/versions.json b/mocks/elixir-trs-filer/tools/versions.json new file mode 100644 index 00000000..818afcc7 --- /dev/null +++ b/mocks/elixir-trs-filer/tools/versions.json @@ -0,0 +1 @@ +[{"id": "v1", "name": "Version 1.0", "author": ["Mock Author"]}] \ No newline at end of file diff --git a/mocks/ga4gh-drs/objects/access.json b/mocks/ga4gh-drs/objects/access.json new file mode 100644 index 00000000..494b4195 --- /dev/null +++ b/mocks/ga4gh-drs/objects/access.json @@ -0,0 +1,4 @@ +{ + "url": "https://example.com/mock-drs-file.txt", + "headers": [] +} \ No newline at end of file diff --git a/mocks/ga4gh-drs/objects/get.json b/mocks/ga4gh-drs/objects/get.json new file mode 100644 index 00000000..2a263a76 --- /dev/null +++ b/mocks/ga4gh-drs/objects/get.json @@ -0,0 +1,24 @@ +{ + "objects": [ + { + "id": "mock-drs-001", + "name": "Mock DRS Object 1", + "size": 1024, + "created_time": "2023-01-01T00:00:00Z", + "updated_time": "2023-01-01T00:00:00Z", + "version": "1.0", + "mime_type": "text/plain", + "checksums": [ + { + "type": "md5", + "checksum": "d41d8cd98f00b204e9800998ecf8427e" + } + ] + } + ], + "pagination": { + "offset": 0, + "limit": 10, + "total": 1 + } +} \ No newline at end of file diff --git a/mocks/ga4gh-drs/objects/id.json b/mocks/ga4gh-drs/objects/id.json new file mode 100644 index 00000000..f832db7c --- /dev/null +++ b/mocks/ga4gh-drs/objects/id.json @@ -0,0 +1,22 @@ +{ + "id": "mock-drs-001", + "name": "Mock DRS Object 1", + "size": 1024, + "created_time": "2023-01-01T00:00:00Z", + "updated_time": "2023-01-01T00:00:00Z", + "version": "1.0", + "mime_type": "text/plain", + "checksums": [ + { + "type": "md5", + "checksum": "d41d8cd98f00b204e9800998ecf8427e" + } + ], + "access_methods": [ + { + "access_id": "access-001", + "type": "s3", + "region": "us-east-1" + } + ] +} \ No newline at end of file diff --git a/mocks/ga4gh-drs/service-info.json b/mocks/ga4gh-drs/service-info.json new file mode 100644 index 00000000..984fc00e --- /dev/null +++ b/mocks/ga4gh-drs/service-info.json @@ -0,0 +1,14 @@ +{ + "id": "mock-ga4gh-drs-service", + "name": "Mock GA4GH DRS Service", + "type": { + "group": "org.ga4gh", + "artifact": "drs", + "version": "1.1.0" + }, + "organization": { + "name": "Mock Org", + "url": "https://example.com" + }, + "version": "1.1.0" +} \ No newline at end of file diff --git a/mocks/ga4gh-service-registry/service-info.json b/mocks/ga4gh-service-registry/service-info.json new file mode 100644 index 00000000..db0a25b9 --- /dev/null +++ b/mocks/ga4gh-service-registry/service-info.json @@ -0,0 +1,14 @@ +{ + "id": "mock-registry-001", + "name": "Mock Service Registry", + "type": { + "group": "org.ga4gh", + "artifact": "service-registry", + "version": "1.0.0" + }, + "organization": { + "name": "ELIXIR Mock", + "url": "https://example.com" + }, + "version": "1.0.0" +} \ No newline at end of file diff --git a/mocks/ga4gh-service-registry/services/get.json b/mocks/ga4gh-service-registry/services/get.json new file mode 100644 index 00000000..3f1489ee --- /dev/null +++ b/mocks/ga4gh-service-registry/services/get.json @@ -0,0 +1,17 @@ +[ + { + "id": "mock-service-001", + "name": "Mock Service 1", + "type": { + "group": "org.ga4gh", + "artifact": "tes", + "version": "1.0.0" + }, + "organization": { + "name": "ELIXIR Mock", + "url": "https://example.com" + }, + "version": "1.0.0", + "url": "https://example.com/tes/v1" + } +] \ No newline at end of file diff --git a/mocks/ga4gh-service-registry/services/id.json b/mocks/ga4gh-service-registry/services/id.json new file mode 100644 index 00000000..84f3b5a7 --- /dev/null +++ b/mocks/ga4gh-service-registry/services/id.json @@ -0,0 +1,15 @@ +{ + "id": "mock-service-001", + "name": "Mock Service 1", + "type": { + "group": "org.ga4gh", + "artifact": "tes", + "version": "1.0.0" + }, + "organization": { + "name": "ELIXIR Mock", + "url": "https://example.com" + }, + "version": "1.0.0", + "url": "https://example.com/tes/v1" +} \ No newline at end of file diff --git a/mocks/ga4gh-service-registry/services/types.json b/mocks/ga4gh-service-registry/services/types.json new file mode 100644 index 00000000..b0912be8 --- /dev/null +++ b/mocks/ga4gh-service-registry/services/types.json @@ -0,0 +1,12 @@ +[ + { + "group": "org.ga4gh", + "artifact": "tes", + "version": "1.0.0" + }, + { + "group": "org.ga4gh", + "artifact": "wes", + "version": "1.0.0" + } +] \ No newline at end of file diff --git a/mocks/ga4gh-tes/service-info.json b/mocks/ga4gh-tes/service-info.json new file mode 100644 index 00000000..2693bd14 --- /dev/null +++ b/mocks/ga4gh-tes/service-info.json @@ -0,0 +1,19 @@ +{ + "contactUrl": "mailto:support@elixir-europe.org", + "createdAt": "2023-01-01T00:00:00Z", + "description": "Mock TES Service", + "environment": "test", + "id": "mock-tes-service", + "name": "Mock TES", + "organization": { + "name": "ELIXIR", + "url": "https://elixir-europe.org" + }, + "type": { + "artifact": "tes", + "group": "org.ga4gh", + "version": "1.0.0" + }, + "updatedAt": "2023-01-01T00:00:00Z", + "version": "1.0.0" +} \ No newline at end of file diff --git a/mocks/ga4gh-tes/tasks/cancel.json b/mocks/ga4gh-tes/tasks/cancel.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/mocks/ga4gh-tes/tasks/cancel.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/mocks/ga4gh-tes/tasks/get.json b/mocks/ga4gh-tes/tasks/get.json new file mode 100644 index 00000000..6fa54af2 --- /dev/null +++ b/mocks/ga4gh-tes/tasks/get.json @@ -0,0 +1,17 @@ +{ + "tasks": [ + { + "id": "task-001", + "state": "COMPLETE" + }, + { + "id": "task-002", + "state": "RUNNING" + }, + { + "id": "task-003", + "state": "ERROR" + } + ], + "next_page_token": "" +} \ No newline at end of file diff --git a/mocks/ga4gh-tes/tasks/id.json b/mocks/ga4gh-tes/tasks/id.json new file mode 100644 index 00000000..9ae6ac53 --- /dev/null +++ b/mocks/ga4gh-tes/tasks/id.json @@ -0,0 +1,28 @@ +{ + "id": "task-001", + "state": "COMPLETE", + "name": "Mock Task 001", + "description": "A mocked task execution", + "executors": [ + { + "image": "ubuntu", + "command": [ + "echo", + "Mock TES" + ] + } + ], + "logs": [ + { + "logs": [ + { + "start_time": "2023-01-01T12:00:00Z", + "end_time": "2023-01-01T12:05:00Z", + "stdout": "Mock TES", + "exit_code": 0 + } + ] + } + ], + "creation_time": "2023-01-01T12:00:00Z" +} \ No newline at end of file diff --git a/mocks/ga4gh-tes/tasks/post.json b/mocks/ga4gh-tes/tasks/post.json new file mode 100644 index 00000000..a820b3b5 --- /dev/null +++ b/mocks/ga4gh-tes/tasks/post.json @@ -0,0 +1,3 @@ +{ + "id": "task-004-newly-created" +} \ No newline at end of file diff --git a/mocks/ga4gh-trs/toolClasses/get.json b/mocks/ga4gh-trs/toolClasses/get.json new file mode 100644 index 00000000..14251aef --- /dev/null +++ b/mocks/ga4gh-trs/toolClasses/get.json @@ -0,0 +1 @@ +[{"id": "mock-class", "name": "Mock Class", "description": "Mock class description"}] \ No newline at end of file diff --git a/mocks/ga4gh-trs/tools/containerfile.json b/mocks/ga4gh-trs/tools/containerfile.json new file mode 100644 index 00000000..d57ddcd4 --- /dev/null +++ b/mocks/ga4gh-trs/tools/containerfile.json @@ -0,0 +1 @@ +[{"content": "FROM ubuntu:latest\nRUN echo mock", "url": "/mocks/ga4gh-trs/tools/mock-content.txt"}] \ No newline at end of file diff --git a/mocks/ga4gh-trs/tools/descriptor-path.json b/mocks/ga4gh-trs/tools/descriptor-path.json new file mode 100644 index 00000000..53f38efa --- /dev/null +++ b/mocks/ga4gh-trs/tools/descriptor-path.json @@ -0,0 +1 @@ +{"content": "mock descriptor by path", "url": "/mocks/ga4gh-trs/tools/mock-content.txt"} \ No newline at end of file diff --git a/mocks/ga4gh-trs/tools/descriptor.json b/mocks/ga4gh-trs/tools/descriptor.json new file mode 100644 index 00000000..61b86635 --- /dev/null +++ b/mocks/ga4gh-trs/tools/descriptor.json @@ -0,0 +1 @@ +{"content": "mock descriptor content", "url": "/mocks/ga4gh-trs/tools/mock-content.txt"} \ No newline at end of file diff --git a/mocks/ga4gh-trs/tools/files.json b/mocks/ga4gh-trs/tools/files.json new file mode 100644 index 00000000..4850643d --- /dev/null +++ b/mocks/ga4gh-trs/tools/files.json @@ -0,0 +1 @@ +[{"path": "/mock/file.txt", "file_type": "TEST_FILE"}] \ No newline at end of file diff --git a/mocks/ga4gh-trs/tools/get.json b/mocks/ga4gh-trs/tools/get.json new file mode 100644 index 00000000..95db1bb0 --- /dev/null +++ b/mocks/ga4gh-trs/tools/get.json @@ -0,0 +1 @@ +[{"id": "mock-tool-001", "name": "Mock Tool", "organization": "Mock Org"}] \ No newline at end of file diff --git a/mocks/ga4gh-trs/tools/id.json b/mocks/ga4gh-trs/tools/id.json new file mode 100644 index 00000000..6fccdf04 --- /dev/null +++ b/mocks/ga4gh-trs/tools/id.json @@ -0,0 +1 @@ +{"id": "mock-tool-001", "name": "Mock Tool", "organization": "Mock Org", "description": "A mocked tool"} \ No newline at end of file diff --git a/mocks/ga4gh-trs/tools/mock-content.txt b/mocks/ga4gh-trs/tools/mock-content.txt new file mode 100644 index 00000000..2b26ae90 --- /dev/null +++ b/mocks/ga4gh-trs/tools/mock-content.txt @@ -0,0 +1 @@ +Mocked file content for GA4GH TRS file wrappers. \ No newline at end of file diff --git a/mocks/ga4gh-trs/tools/tests.json b/mocks/ga4gh-trs/tools/tests.json new file mode 100644 index 00000000..b98a2ca4 --- /dev/null +++ b/mocks/ga4gh-trs/tools/tests.json @@ -0,0 +1 @@ +[{"content": "mock test content", "url": "/mocks/ga4gh-trs/tools/mock-content.txt"}] \ No newline at end of file diff --git a/mocks/ga4gh-trs/tools/version-id.json b/mocks/ga4gh-trs/tools/version-id.json new file mode 100644 index 00000000..feae7634 --- /dev/null +++ b/mocks/ga4gh-trs/tools/version-id.json @@ -0,0 +1 @@ +{"id": "v1", "name": "Version 1.0", "author": ["Mock Author"], "is_production": true} \ No newline at end of file diff --git a/mocks/ga4gh-trs/tools/versions.json b/mocks/ga4gh-trs/tools/versions.json new file mode 100644 index 00000000..818afcc7 --- /dev/null +++ b/mocks/ga4gh-trs/tools/versions.json @@ -0,0 +1 @@ +[{"id": "v1", "name": "Version 1.0", "author": ["Mock Author"]}] \ No newline at end of file diff --git a/mocks/ga4gh-wes/runs/cancel.json b/mocks/ga4gh-wes/runs/cancel.json new file mode 100644 index 00000000..beb78555 --- /dev/null +++ b/mocks/ga4gh-wes/runs/cancel.json @@ -0,0 +1,3 @@ +{ + "run_id": "run-cancelled-001" +} \ No newline at end of file diff --git a/mocks/ga4gh-wes/runs/get.json b/mocks/ga4gh-wes/runs/get.json new file mode 100644 index 00000000..c10d6069 --- /dev/null +++ b/mocks/ga4gh-wes/runs/get.json @@ -0,0 +1,17 @@ +{ + "runs": [ + { + "run_id": "run-001", + "state": "COMPLETE" + }, + { + "run_id": "run-002", + "state": "RUNNING" + }, + { + "run_id": "run-003", + "state": "ERROR" + } + ], + "next_page_token": "" +} \ No newline at end of file diff --git a/mocks/ga4gh-wes/runs/id.json b/mocks/ga4gh-wes/runs/id.json new file mode 100644 index 00000000..024cfff0 --- /dev/null +++ b/mocks/ga4gh-wes/runs/id.json @@ -0,0 +1,40 @@ +{ + "run_id": "run-001", + "state": "COMPLETE", + "request": { + "workflow_params": { + "input_file": "s3://mock-bucket/input.txt" + }, + "workflow_type": "CWL", + "workflow_type_version": "v1.0", + "tags": { + "env": "mock" + }, + "workflow_engine_parameters": {}, + "workflow_url": "https://github.com/mock-org/mock-workflow.cwl" + }, + "run_log": { + "name": "Mock Run 001", + "cmd": [ + "echo", + "mock" + ], + "start_time": "2023-01-01T12:00:00Z", + "end_time": "2023-01-01T12:05:00Z", + "stdout": "Mock successful run", + "stderr": "", + "exit_code": 0 + }, + "task_logs": [ + { + "name": "task-1", + "start_time": "2023-01-01T12:01:00Z", + "end_time": "2023-01-01T12:04:00Z", + "stdout": "Task 1 completed", + "exit_code": 0 + } + ], + "outputs": { + "output_file": "s3://mock-bucket/output.txt" + } +} \ No newline at end of file diff --git a/mocks/ga4gh-wes/runs/post.json b/mocks/ga4gh-wes/runs/post.json new file mode 100644 index 00000000..0b9b5803 --- /dev/null +++ b/mocks/ga4gh-wes/runs/post.json @@ -0,0 +1,3 @@ +{ + "run_id": "run-004-newly-created" +} \ No newline at end of file diff --git a/mocks/ga4gh-wes/runs/status.json b/mocks/ga4gh-wes/runs/status.json new file mode 100644 index 00000000..98e6bc9b --- /dev/null +++ b/mocks/ga4gh-wes/runs/status.json @@ -0,0 +1,4 @@ +{ + "run_id": "run-001", + "state": "COMPLETE" +} \ No newline at end of file diff --git a/mocks/ga4gh-wes/service-info.json b/mocks/ga4gh-wes/service-info.json new file mode 100644 index 00000000..6834e46d --- /dev/null +++ b/mocks/ga4gh-wes/service-info.json @@ -0,0 +1,19 @@ +{ + "contactUrl": "mailto:support@elixir-europe.org", + "createdAt": "2023-01-01T00:00:00Z", + "description": "Mock WES Service", + "environment": "test", + "id": "mock-wes-service", + "name": "Mock WES", + "organization": { + "name": "ELIXIR", + "url": "https://elixir-europe.org" + }, + "type": { + "artifact": "wes", + "group": "org.ga4gh", + "version": "1.0.0" + }, + "updatedAt": "2023-01-01T00:00:00Z", + "version": "1.0.0" +} \ No newline at end of file diff --git a/packages/ecc-client-elixir-cloud-registry/src/providers/rest-cr-provider.ts b/packages/ecc-client-elixir-cloud-registry/src/providers/rest-cr-provider.ts index be24c785..2cff62d3 100644 --- a/packages/ecc-client-elixir-cloud-registry/src/providers/rest-cr-provider.ts +++ b/packages/ecc-client-elixir-cloud-registry/src/providers/rest-cr-provider.ts @@ -6,6 +6,7 @@ import { ExternalServiceRegister, ServiceRegister, } from "./cr-provider.js"; +import { fetcher } from "@elixir-cloud/design"; /** * Implementation of the CloudRegistryProvider interface using direct REST API calls @@ -13,7 +14,7 @@ import { */ export class RestCloudRegistryProvider implements CloudRegistryProvider { // eslint-disable-next-line no-useless-constructor - constructor(public readonly baseUrl: string) {} + constructor(public readonly baseUrl: string) { } /** * Fetch list of services from the registry @@ -21,7 +22,7 @@ export class RestCloudRegistryProvider implements CloudRegistryProvider { */ async getServices(): Promise { const url = `${this.baseUrl}/services`; - const response = await fetch(url); + const response = await fetcher(url, undefined, "elixir-cloud-registry/services/get"); if (!response.ok) { throw new Error(`Failed to fetch services: ${response.statusText}`); } @@ -36,7 +37,7 @@ export class RestCloudRegistryProvider implements CloudRegistryProvider { async getServiceById(serviceId: string): Promise { const encodedServiceId = encodeURIComponent(serviceId); const url = `${this.baseUrl}/services/${encodedServiceId}`; - const response = await fetch(url); + const response = await fetcher(url, undefined, "elixir-cloud-registry/services/id"); if (!response.ok) { throw new Error(`Failed to fetch service: ${response.statusText}`); } @@ -49,7 +50,7 @@ export class RestCloudRegistryProvider implements CloudRegistryProvider { */ async getServiceTypes(): Promise { const url = `${this.baseUrl}/services/types`; - const response = await fetch(url); + const response = await fetcher(url, undefined, "elixir-cloud-registry/services/types"); if (!response.ok) { throw new Error(`Failed to fetch service types: ${response.statusText}`); } @@ -62,7 +63,7 @@ export class RestCloudRegistryProvider implements CloudRegistryProvider { */ async getServiceInfo(): Promise { const url = `${this.baseUrl}/service-info`; - const response = await fetch(url); + const response = await fetcher(url, undefined, "elixir-cloud-registry/service-info"); if (!response.ok) { throw new Error(`Failed to fetch service info: ${response.statusText}`); } @@ -76,13 +77,13 @@ export class RestCloudRegistryProvider implements CloudRegistryProvider { */ async createService(service: ExternalServiceRegister): Promise { const url = `${this.baseUrl}/services`; - const response = await fetch(url, { + const response = await fetcher(url, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(service), - }); + }, "elixir-cloud-registry/services/post"); if (!response.ok) { throw new Error(`Failed to create service: ${response.statusText}`); @@ -103,13 +104,13 @@ export class RestCloudRegistryProvider implements CloudRegistryProvider { ): Promise { const encodedServiceId = encodeURIComponent(id); const url = `${this.baseUrl}/services/${encodedServiceId}`; - const response = await fetch(url, { + const response = await fetcher(url, { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify(service), - }); + }, "elixir-cloud-registry/services/put"); if (!response.ok) { throw new Error( @@ -132,13 +133,13 @@ export class RestCloudRegistryProvider implements CloudRegistryProvider { ): Promise { const encodedServiceId = encodeURIComponent(id); const url = `${this.baseUrl}/services/${encodedServiceId}`; - const response = await fetch(url, { + const response = await fetcher(url, { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify(service), - }); + }, "elixir-cloud-registry/services/put"); if (!response.ok) { throw new Error(`Failed to update service: ${response.statusText}`); @@ -155,9 +156,9 @@ export class RestCloudRegistryProvider implements CloudRegistryProvider { async deleteService(id: string): Promise { const encodedServiceId = encodeURIComponent(id); const url = `${this.baseUrl}/services/${encodedServiceId}`; - const response = await fetch(url, { + const response = await fetcher(url, { method: "DELETE", - }); + }, "elixir-cloud-registry/services/delete"); if (!response.ok) { throw new Error(`Failed to delete service: ${response.statusText}`); @@ -173,13 +174,13 @@ export class RestCloudRegistryProvider implements CloudRegistryProvider { */ async createOrUpdateServiceInfo(service: ServiceRegister): Promise { const url = `${this.baseUrl}/service-info`; - const response = await fetch(url, { + const response = await fetcher(url, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(service), - }); + }, "elixir-cloud-registry/service-info-post"); if (!response.ok) { throw new Error( diff --git a/packages/ecc-client-elixir-drs-filer/src/providers/rest-drs-filer-provider.ts b/packages/ecc-client-elixir-drs-filer/src/providers/rest-drs-filer-provider.ts index 57f1e99e..e1e7924b 100644 --- a/packages/ecc-client-elixir-drs-filer/src/providers/rest-drs-filer-provider.ts +++ b/packages/ecc-client-elixir-drs-filer/src/providers/rest-drs-filer-provider.ts @@ -6,6 +6,7 @@ import { DrsObjectRegister, ServiceRegister, } from "./drs-filer-provider.js"; +import { fetcher } from "@elixir-cloud/design"; /** * Implementation of the DrsFilerProvider interface using direct REST API calls @@ -13,7 +14,7 @@ import { */ export class RestDrsFilerProvider implements DrsFilerProvider { // eslint-disable-next-line no-useless-constructor - constructor(public readonly baseUrl: string) {} + constructor(public readonly baseUrl: string) { } // Base DRS operations (inherited from DrsProvider) async getObjects( @@ -42,7 +43,7 @@ export class RestDrsFilerProvider implements DrsFilerProvider { url += `?${params.toString()}`; } - const response = await fetch(url); + const response = await fetcher(url, undefined, "elixir-drs-filer/objects/get"); if (!response.ok) { throw new Error(`Failed to fetch objects: ${response.statusText}`); } @@ -62,7 +63,7 @@ export class RestDrsFilerProvider implements DrsFilerProvider { url += "?expand=true"; } - const response = await fetch(url); + const response = await fetcher(url, undefined, "elixir-drs-filer/objects/id"); if (!response.ok) { throw new Error(`Failed to fetch object: ${response.statusText}`); } @@ -72,8 +73,10 @@ export class RestDrsFilerProvider implements DrsFilerProvider { async getAccessURL(objectId: string, accessId: string): Promise { const encodedObjectId = encodeURIComponent(objectId); const encodedAccessId = encodeURIComponent(accessId); - const response = await fetch( - `${this.baseUrl}/objects/${encodedObjectId}/access/${encodedAccessId}` + const response = await fetcher( + `${this.baseUrl}/objects/${encodedObjectId}/access/${encodedAccessId}`, + undefined, + "elixir-drs-filer/objects/access" ); if (!response.ok) { throw new Error(`Failed to fetch access URL: ${response.statusText}`); @@ -82,7 +85,7 @@ export class RestDrsFilerProvider implements DrsFilerProvider { } async getServiceInfo(): Promise { - const response = await fetch(`${this.baseUrl}/service-info`); + const response = await fetcher(`${this.baseUrl}/service-info`, undefined, "elixir-drs-filer/service-info"); if (!response.ok) { throw new Error(`Failed to fetch service info: ${response.statusText}`); } @@ -91,13 +94,13 @@ export class RestDrsFilerProvider implements DrsFilerProvider { // DRS-Filer specific operations async createObject(object: DrsObjectRegister): Promise { - const response = await fetch(`${this.baseUrl}/objects`, { + const response = await fetcher(`${this.baseUrl}/objects`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(object), - }); + }, "elixir-drs-filer/objects/post"); if (!response.ok) { throw new Error(`Failed to create object: ${response.statusText}`); @@ -112,13 +115,13 @@ export class RestDrsFilerProvider implements DrsFilerProvider { object: DrsObjectRegister ): Promise { const encodedObjectId = encodeURIComponent(id); - const response = await fetch(`${this.baseUrl}/objects/${encodedObjectId}`, { + const response = await fetcher(`${this.baseUrl}/objects/${encodedObjectId}`, { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify(object), - }); + }, "elixir-drs-filer/objects/put"); if (!response.ok) { throw new Error(`Failed to create/update object: ${response.statusText}`); @@ -130,13 +133,13 @@ export class RestDrsFilerProvider implements DrsFilerProvider { async updateObject(id: string, object: DrsObjectRegister): Promise { const encodedObjectId = encodeURIComponent(id); - const response = await fetch(`${this.baseUrl}/objects/${encodedObjectId}`, { + const response = await fetcher(`${this.baseUrl}/objects/${encodedObjectId}`, { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify(object), - }); + }, "elixir-drs-filer/objects/put"); if (!response.ok) { throw new Error(`Failed to update object: ${response.statusText}`); @@ -148,9 +151,9 @@ export class RestDrsFilerProvider implements DrsFilerProvider { async deleteObject(id: string): Promise { const encodedObjectId = encodeURIComponent(id); - const response = await fetch(`${this.baseUrl}/objects/${encodedObjectId}`, { + const response = await fetcher(`${this.baseUrl}/objects/${encodedObjectId}`, { method: "DELETE", - }); + }, "elixir-drs-filer/objects/delete"); if (!response.ok) { throw new Error(`Failed to delete object: ${response.statusText}`); @@ -160,11 +163,12 @@ export class RestDrsFilerProvider implements DrsFilerProvider { async deleteAccessMethod(objectId: string, accessId: string): Promise { const encodedObjectId = encodeURIComponent(objectId); const encodedAccessId = encodeURIComponent(accessId); - const response = await fetch( + const response = await fetcher( `${this.baseUrl}/objects/${encodedObjectId}/access/${encodedAccessId}`, { method: "DELETE", - } + }, + "elixir-drs-filer/objects/access/delete" ); if (!response.ok) { @@ -173,13 +177,13 @@ export class RestDrsFilerProvider implements DrsFilerProvider { } async updateServiceInfo(service: ServiceRegister): Promise { - const response = await fetch(`${this.baseUrl}/service-info`, { + const response = await fetcher(`${this.baseUrl}/service-info`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(service), - }); + }, "elixir-drs-filer/service-info-post"); if (!response.ok) { throw new Error(`Failed to update service info: ${response.statusText}`); diff --git a/packages/ecc-client-elixir-trs-filer/src/providers/rest-trs-filer-provider.ts b/packages/ecc-client-elixir-trs-filer/src/providers/rest-trs-filer-provider.ts index a0d6de58..e2ba40ea 100644 --- a/packages/ecc-client-elixir-trs-filer/src/providers/rest-trs-filer-provider.ts +++ b/packages/ecc-client-elixir-trs-filer/src/providers/rest-trs-filer-provider.ts @@ -10,6 +10,7 @@ import { ToolClassRegister, ToolVersionRegister, } from "./trs-filer-provider.js"; +import { fetcher } from "@elixir-cloud/design"; /** * Implementation of the TrsFilerProvider interface using direct REST API calls @@ -17,11 +18,11 @@ import { */ export class RestTrsFilerProvider implements TrsFilerProvider { // eslint-disable-next-line no-useless-constructor - constructor(public readonly baseUrl: string) {} + constructor(public readonly baseUrl: string) { } async getToolClasses(): Promise { const url = `${this.baseUrl}/toolClasses`; - const response = await fetch(url); + const response = await fetcher(url, undefined, "elixir-trs-filer/toolClasses/get"); if (!response.ok) { throw new Error(`Failed to fetch tool classes: ${response.statusText}`); } @@ -34,9 +35,8 @@ export class RestTrsFilerProvider implements TrsFilerProvider { filters: Record, query: string ): Promise { - let url = `${this.baseUrl}/tools?${limit ? `limit=${limit}&` : ""}${ - offset ? `offset=${offset}&` : "" - }`; + let url = `${this.baseUrl}/tools?${limit ? `limit=${limit}&` : ""}${offset ? `offset=${offset}&` : "" + }`; // Add search query if provided if (query && query.length > 0) { @@ -50,7 +50,7 @@ export class RestTrsFilerProvider implements TrsFilerProvider { } }); - const response = await fetch(url); + const response = await fetcher(url, undefined, "elixir-trs-filer/tools/get"); if (!response.ok) { throw new Error(`Failed to fetch tools: ${response.statusText}`); } @@ -59,7 +59,7 @@ export class RestTrsFilerProvider implements TrsFilerProvider { async getTool(url: string, id: string): Promise { const encodedToolId = encodeURIComponent(id); - const response = await fetch(`${this.baseUrl}/tools/${encodedToolId}`); + const response = await fetcher(`${this.baseUrl}/tools/${encodedToolId}`, undefined, "elixir-trs-filer/tools/id"); if (!response.ok) { throw new Error(`Failed to fetch tool: ${response.statusText}`); } @@ -68,8 +68,10 @@ export class RestTrsFilerProvider implements TrsFilerProvider { async getToolVersions(url: string, id: string): Promise { const encodedToolId = encodeURIComponent(id); - const response = await fetch( - `${this.baseUrl}/tools/${encodedToolId}/versions` + const response = await fetcher( + `${this.baseUrl}/tools/${encodedToolId}/versions`, + undefined, + "elixir-trs-filer/tools/versions" ); if (!response.ok) { throw new Error(`Failed to fetch tool versions: ${response.statusText}`); @@ -87,8 +89,10 @@ export class RestTrsFilerProvider implements TrsFilerProvider { ? versionId.split(":")[1] : versionId; const encodedVersionId = encodeURIComponent(version); - const response = await fetch( - `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}` + const response = await fetcher( + `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}`, + undefined, + "elixir-trs-filer/tools/version-id" ); if (!response.ok) { throw new Error(`Failed to fetch tool version: ${response.statusText}`); @@ -112,7 +116,7 @@ export class RestTrsFilerProvider implements TrsFilerProvider { if (format) { requestUrl += `?format=${format}`; } - const response = await fetch(requestUrl); + const response = await fetcher(requestUrl, undefined, "elixir-trs-filer/tools/files"); if (!response.ok) { throw new Error(`Failed to fetch tool files: ${response.statusText}`); } @@ -129,8 +133,10 @@ export class RestTrsFilerProvider implements TrsFilerProvider { const versionPart = version.split(":")[1] ? version.split(":")[1] : version; const encodedVersionId = encodeURIComponent(versionPart); const encodedType = encodeURIComponent(descriptorType); - const response = await fetch( - `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}/${encodedType}/descriptor` + const response = await fetcher( + `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}/${encodedType}/descriptor`, + undefined, + "elixir-trs-filer/tools/descriptor" ); if (!response.ok) { throw new Error( @@ -154,8 +160,10 @@ export class RestTrsFilerProvider implements TrsFilerProvider { // Try with unencoded path first try { - const response = await fetch( - `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}/${encodedType}/descriptor/${path}` + const response = await fetcher( + `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}/${encodedType}/descriptor/${path}`, + undefined, + "elixir-trs-filer/tools/descriptor-path" ); if (response.ok) { @@ -164,8 +172,10 @@ export class RestTrsFilerProvider implements TrsFilerProvider { // If unencoded path fails, try with encoded path const encodedPath = encodeURIComponent(path); - const encodedResponse = await fetch( - `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}/${encodedType}/descriptor/${encodedPath}` + const encodedResponse = await fetcher( + `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}/${encodedType}/descriptor/${encodedPath}`, + undefined, + "elixir-trs-filer/tools/descriptor-path" ); if (encodedResponse.ok) { @@ -180,8 +190,10 @@ export class RestTrsFilerProvider implements TrsFilerProvider { // If there's a network error or other exception, try encoded path const encodedPath = encodeURIComponent(path); try { - const encodedResponse = await fetch( - `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}/${encodedType}/descriptor/${encodedPath}` + const encodedResponse = await fetcher( + `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}/${encodedType}/descriptor/${encodedPath}`, + undefined, + "elixir-trs-filer/tools/descriptor-path" ); if (encodedResponse.ok) { @@ -206,8 +218,10 @@ export class RestTrsFilerProvider implements TrsFilerProvider { const encodedToolId = encodeURIComponent(id); const versionPart = version.split(":")[1] ? version.split(":")[1] : version; const encodedVersionId = encodeURIComponent(versionPart); - const response = await fetch( - `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}/containerfile` + const response = await fetcher( + `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}/containerfile`, + undefined, + "elixir-trs-filer/tools/containerfile" ); if (!response.ok) { throw new Error(`Failed to fetch containerfile: ${response.statusText}`); @@ -225,8 +239,10 @@ export class RestTrsFilerProvider implements TrsFilerProvider { const versionPart = version.split(":")[1] ? version.split(":")[1] : version; const encodedVersionId = encodeURIComponent(versionPart); const encodedType = encodeURIComponent(descriptorType); - const response = await fetch( - `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}/${encodedType}/tests` + const response = await fetcher( + `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}/${encodedType}/tests`, + undefined, + "elixir-trs-filer/tools/tests" ); if (!response.ok) { throw new Error(`Failed to fetch tool tests: ${response.statusText}`); @@ -237,13 +253,13 @@ export class RestTrsFilerProvider implements TrsFilerProvider { // Creation methods (TRS-Filer specific functionality) async createTool(tool: ToolRegister): Promise { console.log("Creating tool:", tool); - const response = await fetch(`${this.baseUrl}/tools`, { + const response = await fetcher(`${this.baseUrl}/tools`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(tool), - }); + }, "elixir-trs-filer/tools/post"); if (!response.ok) { throw new Error(`Failed to create tool: ${response.statusText}`); @@ -254,13 +270,13 @@ export class RestTrsFilerProvider implements TrsFilerProvider { async createToolWithId(id: string, tool: ToolRegister): Promise { const encodedToolId = encodeURIComponent(id); - const response = await fetch(`${this.baseUrl}/tools/${encodedToolId}`, { + const response = await fetcher(`${this.baseUrl}/tools/${encodedToolId}`, { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify(tool), - }); + }, "elixir-trs-filer/tools/put"); if (!response.ok) { throw new Error(`Failed to create/update tool: ${response.statusText}`); @@ -274,7 +290,7 @@ export class RestTrsFilerProvider implements TrsFilerProvider { version: ToolVersionRegister ): Promise { const encodedToolId = encodeURIComponent(toolId); - const response = await fetch( + const response = await fetcher( `${this.baseUrl}/tools/${encodedToolId}/versions`, { method: "POST", @@ -282,7 +298,8 @@ export class RestTrsFilerProvider implements TrsFilerProvider { "Content-Type": "application/json", }, body: JSON.stringify(version), - } + }, + "elixir-trs-filer/tools/versions-post" ); if (!response.ok) { @@ -299,7 +316,7 @@ export class RestTrsFilerProvider implements TrsFilerProvider { ): Promise { const encodedToolId = encodeURIComponent(toolId); const encodedVersionId = encodeURIComponent(versionId); - const response = await fetch( + const response = await fetcher( `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}`, { method: "PUT", @@ -307,7 +324,8 @@ export class RestTrsFilerProvider implements TrsFilerProvider { "Content-Type": "application/json", }, body: JSON.stringify(version), - } + }, + "elixir-trs-filer/tools/versions-put" ); if (!response.ok) { @@ -320,13 +338,13 @@ export class RestTrsFilerProvider implements TrsFilerProvider { } async createToolClass(toolClass: ToolClassRegister): Promise { - const response = await fetch(`${this.baseUrl}/toolClasses`, { + const response = await fetcher(`${this.baseUrl}/toolClasses`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(toolClass), - }); + }, "elixir-trs-filer/toolClasses/post"); if (!response.ok) { throw new Error(`Failed to create tool class: ${response.statusText}`); @@ -340,13 +358,13 @@ export class RestTrsFilerProvider implements TrsFilerProvider { toolClass: ToolClassRegister ): Promise { const encodedId = encodeURIComponent(id); - const response = await fetch(`${this.baseUrl}/toolClasses/${encodedId}`, { + const response = await fetcher(`${this.baseUrl}/toolClasses/${encodedId}`, { method: "PUT", headers: { "Content-Type": "application/json", }, body: JSON.stringify(toolClass), - }); + }, "elixir-trs-filer/toolClasses/put"); if (!response.ok) { throw new Error( diff --git a/packages/ecc-client-ga4gh-drs/src/providers/rest-drs-provider.ts b/packages/ecc-client-ga4gh-drs/src/providers/rest-drs-provider.ts index ddbe72bf..5c4f15b3 100644 --- a/packages/ecc-client-ga4gh-drs/src/providers/rest-drs-provider.ts +++ b/packages/ecc-client-ga4gh-drs/src/providers/rest-drs-provider.ts @@ -1,4 +1,8 @@ -import { DrsProvider, DrsObject, AccessURL, Service } from "./drs-provider.js"; +import { + DrsProvider, DrsObject, AccessURL, + Service, +} from "./drs-provider.js"; +import { fetcher } from "@elixir-cloud/design"; /** * Implementation of the DrsProvider interface using direct REST API calls @@ -6,7 +10,7 @@ import { DrsProvider, DrsObject, AccessURL, Service } from "./drs-provider.js"; */ export class RestDrsProvider implements DrsProvider { // eslint-disable-next-line no-useless-constructor - constructor(public readonly baseUrl: string) {} + constructor(public readonly baseUrl: string) { } async getObjects( limit?: number, @@ -34,7 +38,7 @@ export class RestDrsProvider implements DrsProvider { url += `?${params.toString()}`; } - const response = await fetch(url); + const response = await fetcher(url, undefined, "ga4gh-drs/objects/get"); if (!response.ok) { throw new Error(`Failed to fetch objects: ${response.statusText}`); } @@ -54,7 +58,7 @@ export class RestDrsProvider implements DrsProvider { url += "?expand=true"; } - const response = await fetch(url); + const response = await fetcher(url, undefined, "ga4gh-drs/objects/id"); if (!response.ok) { throw new Error(`Failed to fetch object: ${response.statusText}`); } @@ -64,8 +68,10 @@ export class RestDrsProvider implements DrsProvider { async getAccessURL(objectId: string, accessId: string): Promise { const encodedObjectId = encodeURIComponent(objectId); const encodedAccessId = encodeURIComponent(accessId); - const response = await fetch( - `${this.baseUrl}/objects/${encodedObjectId}/access/${encodedAccessId}` + const response = await fetcher( + `${this.baseUrl}/objects/${encodedObjectId}/access/${encodedAccessId}`, + undefined, + "ga4gh-drs/objects/access" ); if (!response.ok) { throw new Error(`Failed to fetch access URL: ${response.statusText}`); @@ -74,7 +80,7 @@ export class RestDrsProvider implements DrsProvider { } async getServiceInfo(): Promise { - const response = await fetch(`${this.baseUrl}/service-info`); + const response = await fetcher(`${this.baseUrl}/service-info`, undefined, "ga4gh-drs/service-info"); if (!response.ok) { throw new Error(`Failed to fetch service info: ${response.statusText}`); } diff --git a/packages/ecc-client-ga4gh-service-registry/src/providers/rest-sr-provider.ts b/packages/ecc-client-ga4gh-service-registry/src/providers/rest-sr-provider.ts index 1a74cba1..246999c4 100644 --- a/packages/ecc-client-ga4gh-service-registry/src/providers/rest-sr-provider.ts +++ b/packages/ecc-client-ga4gh-service-registry/src/providers/rest-sr-provider.ts @@ -4,6 +4,7 @@ import { Service, ServiceType, } from "./sr-provider.js"; +import { fetcher } from "@elixir-cloud/design"; /** * Implementation of the ServiceRegistryProvider interface using direct REST API calls @@ -11,7 +12,7 @@ import { */ export class RestServiceRegistryProvider implements ServiceRegistryProvider { // eslint-disable-next-line no-useless-constructor - constructor(public readonly baseUrl: string) {} + constructor(public readonly baseUrl: string) { } /** * Fetch list of services from the registry @@ -19,7 +20,7 @@ export class RestServiceRegistryProvider implements ServiceRegistryProvider { */ async getServices(): Promise { const url = `${this.baseUrl}/services`; - const response = await fetch(url); + const response = await fetcher(url, undefined, "ga4gh-service-registry/services/get"); if (!response.ok) { throw new Error(`Failed to fetch services: ${response.statusText}`); } @@ -34,7 +35,7 @@ export class RestServiceRegistryProvider implements ServiceRegistryProvider { async getServiceById(serviceId: string): Promise { const encodedServiceId = encodeURIComponent(serviceId); const url = `${this.baseUrl}/services/${encodedServiceId}`; - const response = await fetch(url); + const response = await fetcher(url, undefined, "ga4gh-service-registry/services/id"); if (!response.ok) { throw new Error(`Failed to fetch service: ${response.statusText}`); } @@ -47,7 +48,7 @@ export class RestServiceRegistryProvider implements ServiceRegistryProvider { */ async getServiceTypes(): Promise { const url = `${this.baseUrl}/services/types`; - const response = await fetch(url); + const response = await fetcher(url, undefined, "ga4gh-service-registry/services/types"); if (!response.ok) { throw new Error(`Failed to fetch service types: ${response.statusText}`); } @@ -60,7 +61,7 @@ export class RestServiceRegistryProvider implements ServiceRegistryProvider { */ async getServiceInfo(): Promise { const url = `${this.baseUrl}/service-info`; - const response = await fetch(url); + const response = await fetcher(url, undefined, "ga4gh-service-registry/service-info"); if (!response.ok) { throw new Error(`Failed to fetch service info: ${response.statusText}`); } diff --git a/packages/ecc-client-ga4gh-tes/src/API/Task/tesGet.ts b/packages/ecc-client-ga4gh-tes/src/API/Task/tesGet.ts index 0ef0dd5d..fcb1beb8 100644 --- a/packages/ecc-client-ga4gh-tes/src/API/Task/tesGet.ts +++ b/packages/ecc-client-ga4gh-tes/src/API/Task/tesGet.ts @@ -1,3 +1,5 @@ +import { fetcher } from "@elixir-cloud/design"; + /** * Fetches service-info endpoint * @param baseURL The base URL for fetching tasks @@ -6,8 +8,8 @@ const fetchService = async (baseURL: string) => { const url = `${baseURL}/service-info?`; try { - const response = await fetch(url); - if (!response) { + const response = await fetcher(url, undefined, "ga4gh-tes/service-info"); + if (!response || !response.ok) { return { isError: true, breakpoint: "fetchTasks", @@ -73,8 +75,8 @@ const fetchTasks = async ( } try { - const response = await fetch(url); - if (!response) { + const response = await fetcher(url, undefined, "ga4gh-tes/tasks/get"); + if (!response || !response.ok) { return { isError: true, breakpoint: "fetchTasks", @@ -100,8 +102,8 @@ const fetchTask = async (baseURL: string, id: string) => { const url = `${baseURL}/tasks/${id}?view=FULL`; try { - const response = await fetch(url); - if (!response) { + const response = await fetcher(url, undefined, "ga4gh-tes/tasks/id"); + if (!response || !response.ok) { return { isError: true, breakpoint: "fetchTask", @@ -125,9 +127,9 @@ const fetchTask = async (baseURL: string, id: string) => { const deleteTask = async (baseURL: string, id: string) => { const url = `${baseURL}/tasks/${id}:cancel`; try { - const response = await fetch(url, { + const response = await fetcher(url, { method: "DELETE", - }); + }, "ga4gh-tes/tasks/cancel"); return response; } catch (error) { return { @@ -151,15 +153,15 @@ const postTask = async (baseURL: string, taskData: object) => { const url = `${baseURL}/tasks`; try { - const response = await fetch(url, { + const response = await fetcher(url, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(taskData), - }); + }, "ga4gh-tes/tasks/post"); - if (!response) { + if (!response || !response.ok) { return { isError: true, breakpoint: "postTask", diff --git a/packages/ecc-client-ga4gh-trs/src/components/tool/tool.ts b/packages/ecc-client-ga4gh-trs/src/components/tool/tool.ts index e4078ed7..26ad10d7 100644 --- a/packages/ecc-client-ga4gh-trs/src/components/tool/tool.ts +++ b/packages/ecc-client-ga4gh-trs/src/components/tool/tool.ts @@ -10,6 +10,7 @@ import { DescriptorType, } from "../../providers/trs-provider.js"; import { RestTrsProvider } from "../../providers/rest-trs-provider.js"; +import { fetcher } from "@elixir-cloud/design"; import "@elixir-cloud/design/components/table/index.js"; import "@elixir-cloud/design/components/button/index.js"; import "@elixir-cloud/design/components/input/index.js"; @@ -315,7 +316,7 @@ export class ECCClientGa4ghTrsTool extends LitElement { }; } else if (fileWrapper.url) { // If only URL is provided, fetch the content from the URL - const response = await fetch(fileWrapper.url); + const response = await fetcher(fileWrapper.url); if (response.ok) { const content = await response.text(); // Create a new object to trigger change detection @@ -347,7 +348,7 @@ export class ECCClientGa4ghTrsTool extends LitElement { }; } else if (fileWrapper.url) { // If only URL is provided, fetch the content from the URL - const response = await fetch(fileWrapper.url); + const response = await fetcher(fileWrapper.url); if (response.ok) { const content = await response.text(); // Create a new object to trigger change detection @@ -369,9 +370,8 @@ export class ECCClientGa4ghTrsTool extends LitElement { // Create a new object to trigger change detection this.fileContents = { ...this.fileContents, - [file.path]: `Error loading file content: ${ - err instanceof Error ? err.message : String(err) - }`, + [file.path]: `Error loading file content: ${err instanceof Error ? err.message : String(err) + }`, }; } } @@ -398,7 +398,7 @@ export class ECCClientGa4ghTrsTool extends LitElement { ${this.tool.versions && this.tool.versions.length > 0 - ? html` + ? html`
${versionName} ${isProduction - ? html` + ? html` Production ` - : ""} + : ""}
${this.tool.versions.map( - (version) => html` + (version) => html`
` - )} + )}
` - : ""} + : ""}
${selectedVersion?.is_production - ? html` + ? html` Production ` - : ""} + : ""} ${this.tool.toolclass - ? html` + ? html` ${this.tool.toolclass.name} ` - : ""} + : ""} ${this.getAvailableDescriptorTypes().map( - (type) => html` + (type) => html` ${type} ` - )} + )}
@@ -608,14 +608,14 @@ export class ECCClientGa4ghTrsTool extends LitElement {
${this.tool.description - ? html` + ? html`
${this.tool.description - .split("\n") - .map((line) => html`

${line}

`)} + .split("\n") + .map((line) => html`

${line}

`)}
` - : ""} + : ""}
@@ -632,7 +632,7 @@ export class ECCClientGa4ghTrsTool extends LitElement { @@ -811,11 +811,11 @@ export class ECCClientGa4ghTrsTool extends LitElement { ${this.tool.versions.map( - (version) => html` + (version) => html` this.handleSetActiveVersion(version.id)} > @@ -826,14 +826,14 @@ export class ECCClientGa4ghTrsTool extends LitElement { ${version.name || version.id} ${version.is_production - ? html` + ? html` Production ` - : ""} + : ""}
${version.descriptor_type && - version.descriptor_type.length > 0 - ? version.descriptor_type.map( - (type) => html` + version.descriptor_type.length > 0 + ? version.descriptor_type.map( + (type) => html` ${type} ` - ) - : html`None specified`}
${version.author && version.author.length > 0 - ? html`${version.author.join(", ")}` - : html`Not specified`}
${version.verified - ? html` + ? html` @@ -893,14 +893,14 @@ export class ECCClientGa4ghTrsTool extends LitElement { Verified ` - : html`Not verified`}
` - )} + )}
@@ -930,10 +930,10 @@ export class ECCClientGa4ghTrsTool extends LitElement { .activeFileIndex=${this.activeFileIndex} .fileContents=${this.fileContents} .onDescriptorTypeChange=${(e: CustomEvent) => - this.handleDescriptorTypeChange(e)} + this.handleDescriptorTypeChange(e)} .onFileSelect=${(index: number) => this.viewFileContent(index)} .getAvailableDescriptorTypes=${() => - this.getAvailableDescriptorTypes()} + this.getAvailableDescriptorTypes()} > - this.handleDescriptorTypeChange(e)} + this.handleDescriptorTypeChange(e)} >
Browse Files
@@ -962,12 +962,12 @@ export class ECCClientGa4ghTrsTool extends LitElement { ${this.getAvailableDescriptorTypes().map( - (type) => html` + (type) => html` ${type} ` - )} + )}
@@ -995,27 +995,27 @@ export class ECCClientGa4ghTrsTool extends LitElement { .activeFileIndex=${this.activeFileIndex} .selectedDescriptorType=${this.selectedDescriptorType} .onFileSelect=${(index: number) => - this.viewFileContent(index)} + this.viewFileContent(index)} > ${this.toolFiles.length === 0 - ? html`

+ ? html`

No files available for ${this.selectedDescriptorType}

` - : html` + : html`
${this.toolFiles.map( - (file, index) => html` + (file, index) => html` ` - )} + )}
`}
@@ -1057,27 +1057,27 @@ export class ECCClientGa4ghTrsTool extends LitElement {

${this.activeFileIndex >= 0 && - this.toolFiles[this.activeFileIndex] - ? this.toolFiles[this.activeFileIndex].path - : "File Content"} + this.toolFiles[this.activeFileIndex] + ? this.toolFiles[this.activeFileIndex].path + : "File Content"}

${this.activeFileIndex >= 0 && - this.toolFiles[this.activeFileIndex] - ? html` + this.toolFiles[this.activeFileIndex] + ? html` ` - : html`

+ : html`

Select a file to view its content

`}
diff --git a/packages/ecc-client-ga4gh-trs/src/providers/rest-trs-provider.ts b/packages/ecc-client-ga4gh-trs/src/providers/rest-trs-provider.ts index a2249475..148b60b8 100644 --- a/packages/ecc-client-ga4gh-trs/src/providers/rest-trs-provider.ts +++ b/packages/ecc-client-ga4gh-trs/src/providers/rest-trs-provider.ts @@ -7,6 +7,7 @@ import { ToolVersion, FileWrapper, } from "./trs-provider.js"; +import { fetcher } from "@elixir-cloud/design"; /** * Implementation of the TrsProvider interface using direct REST API calls @@ -14,11 +15,11 @@ import { */ export class RestTrsProvider implements TrsProvider { // eslint-disable-next-line no-useless-constructor - constructor(public readonly baseUrl: string) {} + constructor(public readonly baseUrl: string) { } async getToolClasses(): Promise { const url = `${this.baseUrl}/toolClasses`; - const response = await fetch(url); + const response = await fetcher(url, undefined, "ga4gh-trs/toolClasses/get"); if (!response.ok) { throw new Error(`Failed to fetch tool classes: ${response.statusText}`); } @@ -31,9 +32,8 @@ export class RestTrsProvider implements TrsProvider { filters: Record, query: string ): Promise { - let url = `${this.baseUrl}/tools?${limit ? `limit=${limit}&` : ""}${ - offset ? `offset=${offset}&` : "" - }`; + let url = `${this.baseUrl}/tools?${limit ? `limit=${limit}&` : ""}${offset ? `offset=${offset}&` : "" + }`; // Add search query if provided if (query && query.length > 0) { @@ -47,7 +47,7 @@ export class RestTrsProvider implements TrsProvider { } }); - const response = await fetch(url); + const response = await fetcher(url, undefined, "ga4gh-trs/tools/get"); if (!response.ok) { throw new Error(`Failed to fetch tools: ${response.statusText}`); } @@ -56,7 +56,7 @@ export class RestTrsProvider implements TrsProvider { async getTool(url: string, id: string): Promise { const encodedToolId = encodeURIComponent(id); - const response = await fetch(`${this.baseUrl}/tools/${encodedToolId}`); + const response = await fetcher(`${this.baseUrl}/tools/${encodedToolId}`, undefined, "ga4gh-trs/tools/id"); if (!response.ok) { throw new Error(`Failed to fetch tool: ${response.statusText}`); } @@ -65,8 +65,10 @@ export class RestTrsProvider implements TrsProvider { async getToolVersions(url: string, id: string): Promise { const encodedToolId = encodeURIComponent(id); - const response = await fetch( - `${this.baseUrl}/tools/${encodedToolId}/versions` + const response = await fetcher( + `${this.baseUrl}/tools/${encodedToolId}/versions`, + undefined, + "ga4gh-trs/tools/versions" ); if (!response.ok) { throw new Error(`Failed to fetch tool versions: ${response.statusText}`); @@ -84,8 +86,10 @@ export class RestTrsProvider implements TrsProvider { ? versionId.split(":")[1] : versionId; const encodedVersionId = encodeURIComponent(version); - const response = await fetch( - `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}` + const response = await fetcher( + `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}`, + undefined, + "ga4gh-trs/tools/version-id" ); if (!response.ok) { throw new Error(`Failed to fetch tool version: ${response.statusText}`); @@ -109,7 +113,7 @@ export class RestTrsProvider implements TrsProvider { if (format) { requestUrl += `?format=${format}`; } - const response = await fetch(requestUrl); + const response = await fetcher(requestUrl, undefined, "ga4gh-trs/tools/files"); if (!response.ok) { throw new Error(`Failed to fetch tool files: ${response.statusText}`); } @@ -126,8 +130,10 @@ export class RestTrsProvider implements TrsProvider { const versionPart = version.split(":")[1] ? version.split(":")[1] : version; const encodedVersionId = encodeURIComponent(versionPart); const encodedType = encodeURIComponent(descriptorType); - const response = await fetch( - `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}/${encodedType}/descriptor` + const response = await fetcher( + `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}/${encodedType}/descriptor`, + undefined, + "ga4gh-trs/tools/descriptor" ); if (!response.ok) { throw new Error( @@ -151,8 +157,10 @@ export class RestTrsProvider implements TrsProvider { // Try with unencoded path first try { - const response = await fetch( - `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}/${encodedType}/descriptor/${path}` + const response = await fetcher( + `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}/${encodedType}/descriptor/${path}`, + undefined, + "ga4gh-trs/tools/descriptor-path" ); if (response.ok) { @@ -161,8 +169,10 @@ export class RestTrsProvider implements TrsProvider { // If unencoded path fails, try with encoded path const encodedPath = encodeURIComponent(path); - const encodedResponse = await fetch( - `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}/${encodedType}/descriptor/${encodedPath}` + const encodedResponse = await fetcher( + `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}/${encodedType}/descriptor/${encodedPath}`, + undefined, + "ga4gh-trs/tools/descriptor-path" ); if (encodedResponse.ok) { @@ -177,8 +187,10 @@ export class RestTrsProvider implements TrsProvider { // If there's a network error or other exception, try encoded path const encodedPath = encodeURIComponent(path); try { - const encodedResponse = await fetch( - `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}/${encodedType}/descriptor/${encodedPath}` + const encodedResponse = await fetcher( + `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}/${encodedType}/descriptor/${encodedPath}`, + undefined, + "ga4gh-trs/tools/descriptor-path" ); if (encodedResponse.ok) { @@ -203,8 +215,10 @@ export class RestTrsProvider implements TrsProvider { const encodedToolId = encodeURIComponent(id); const versionPart = version.split(":")[1] ? version.split(":")[1] : version; const encodedVersionId = encodeURIComponent(versionPart); - const response = await fetch( - `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}/containerfile` + const response = await fetcher( + `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}/containerfile`, + undefined, + "ga4gh-trs/tools/containerfile" ); if (!response.ok) { throw new Error(`Failed to fetch containerfile: ${response.statusText}`); @@ -222,8 +236,10 @@ export class RestTrsProvider implements TrsProvider { const versionPart = version.split(":")[1] ? version.split(":")[1] : version; const encodedVersionId = encodeURIComponent(versionPart); const encodedType = encodeURIComponent(descriptorType); - const response = await fetch( - `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}/${encodedType}/tests` + const response = await fetcher( + `${this.baseUrl}/tools/${encodedToolId}/versions/${encodedVersionId}/${encodedType}/tests`, + undefined, + "ga4gh-trs/tools/tests" ); if (!response.ok) { throw new Error(`Failed to fetch tool tests: ${response.statusText}`); diff --git a/packages/ecc-client-ga4gh-wes/src/components/run/run.ts b/packages/ecc-client-ga4gh-wes/src/components/run/run.ts index 4e496b75..c8ca8100 100644 --- a/packages/ecc-client-ga4gh-wes/src/components/run/run.ts +++ b/packages/ecc-client-ga4gh-wes/src/components/run/run.ts @@ -9,6 +9,7 @@ import { Log, } from "../../providers/wes-provider.js"; import { RestWesProvider } from "../../providers/rest-wes-provider.js"; +import { fetcher } from "@elixir-cloud/design"; import "@elixir-cloud/design/components/table/index.js"; import "@elixir-cloud/design/components/button/index.js"; import "@elixir-cloud/design/components/badge/index.js"; @@ -244,15 +245,15 @@ export class ECCClientGa4ghWesRun extends LitElement {
${(() => { - const stateInfo = ECCClientGa4ghWesRun.getStateInfo( - this.run.state - ); - return html` + const stateInfo = ECCClientGa4ghWesRun.getStateInfo( + this.run.state + ); + return html` ${stateInfo.label} `; - })()} + })()} ${this.run.request.workflow_type} @@ -373,15 +374,15 @@ export class ECCClientGa4ghWesRun extends LitElement {
State
${(() => { - const stateInfo = ECCClientGa4ghWesRun.getStateInfo( - this.run.state - ); - return html` + const stateInfo = ECCClientGa4ghWesRun.getStateInfo( + this.run.state + ); + return html` ${stateInfo.label} `; - })()} + })()}
@@ -428,59 +429,59 @@ export class ECCClientGa4ghWesRun extends LitElement {
${request.workflow_engine_parameters - ? html` + ? html`

Engine Parameters

` - : ""} + : ""}
${request.tags - ? (() => { - let tagsObj: Record | null = null; - if (typeof request.tags === "string") { - try { - tagsObj = JSON.parse(request.tags); - } catch { - tagsObj = null; - } - } else if ( - typeof request.tags === "object" && - request.tags !== null - ) { - tagsObj = request.tags; - } - return tagsObj - ? html` + ? (() => { + let tagsObj: Record | null = null; + if (typeof request.tags === "string") { + try { + tagsObj = JSON.parse(request.tags); + } catch { + tagsObj = null; + } + } else if ( + typeof request.tags === "object" && + request.tags !== null + ) { + tagsObj = request.tags; + } + return tagsObj + ? html`
Tags
${Object.entries(tagsObj).map( - ([key, value]) => html` + ([key, value]) => html` ${key}: ${value} ` - )} + )}
` - : ""; - })() - : ""} + : ""; + })() + : ""} `; @@ -516,7 +517,7 @@ export class ECCClientGa4ghWesRun extends LitElement { private async fetchLogUrlToCache(url: string, key: string): Promise { try { - const response = await fetch(url); + const response = await fetcher(url); if (!response.ok) { throw new Error(response.statusText || "Failed to fetch log"); } @@ -525,9 +526,8 @@ export class ECCClientGa4ghWesRun extends LitElement { } catch (err) { this.logContents = { ...this.logContents, - [key]: `Error loading log: ${ - err instanceof Error ? err.message : String(err) - }`, + [key]: `Error loading log: ${err instanceof Error ? err.message : String(err) + }`, }; } } @@ -554,14 +554,14 @@ export class ECCClientGa4ghWesRun extends LitElement {
${log.name - ? html` + ? html`
Name
${log.name}
` - : ""} + : ""}
Start Time
@@ -588,7 +588,7 @@ export class ECCClientGa4ghWesRun extends LitElement {
${log.cmd && log.cmd.length > 0 - ? html` + ? html`
Command
` - : ""} + : ""}
Output Logs
@@ -661,7 +661,7 @@ export class ECCClientGa4ghWesRun extends LitElement { return html`
${this.run.task_logs.map( - (taskLog, index) => html` + (taskLog, index) => html`
` - )} + )}
`; } @@ -768,14 +768,14 @@ export class ECCClientGa4ghWesRun extends LitElement { >Run Log ${this.hasTaskLogs() - ? html` + ? html` Task Logs ` - : ""} + : ""} ${this.hasTaskLogs() - ? html` + ? html` ${this.renderTaskLogsTab()} ` - : ""} + : ""} ${this.renderOutputsTab()} diff --git a/packages/ecc-client-ga4gh-wes/src/providers/rest-wes-provider.ts b/packages/ecc-client-ga4gh-wes/src/providers/rest-wes-provider.ts index 7eb14180..13605c71 100644 --- a/packages/ecc-client-ga4gh-wes/src/providers/rest-wes-provider.ts +++ b/packages/ecc-client-ga4gh-wes/src/providers/rest-wes-provider.ts @@ -8,6 +8,7 @@ import { RunStatus, ErrorResponse, } from "./wes-provider.js"; +import { fetcher } from "@elixir-cloud/design"; /** * REST API implementation of WesProvider @@ -40,7 +41,7 @@ export class RestWesProvider implements WesProvider { } async getServiceInfo(): Promise { - const response = await fetch(`${this.baseUrl}/service-info`); + const response = await fetcher(`${this.baseUrl}/service-info`, undefined, "ga4gh-wes/service-info"); return RestWesProvider.handleResponse(response); } @@ -58,10 +59,9 @@ export class RestWesProvider implements WesProvider { params.append("page_token", pageToken); } - const url = `${this.baseUrl}/runs${ - params.toString() ? `?${params.toString()}` : "" - }`; - const response = await fetch(url); + const url = `${this.baseUrl}/runs${params.toString() ? `?${params.toString()}` : "" + }`; + const response = await fetcher(url, undefined, "ga4gh-wes/runs/get"); return RestWesProvider.handleResponse(response); } @@ -93,34 +93,39 @@ export class RestWesProvider implements WesProvider { }); } - const response = await fetch(`${this.baseUrl}/runs`, { + const response = await fetcher(`${this.baseUrl}/runs`, { method: "POST", body: formData, - }); + }, "ga4gh-wes/runs/post"); return RestWesProvider.handleResponse(response); } async getRunLog(runId: string): Promise { - const response = await fetch( - `${this.baseUrl}/runs/${encodeURIComponent(runId)}` + const response = await fetcher( + `${this.baseUrl}/runs/${encodeURIComponent(runId)}`, + undefined, + "ga4gh-wes/runs/id" ); return RestWesProvider.handleResponse(response); } async getRunStatus(runId: string): Promise { - const response = await fetch( - `${this.baseUrl}/runs/${encodeURIComponent(runId)}/status` + const response = await fetcher( + `${this.baseUrl}/runs/${encodeURIComponent(runId)}/status`, + undefined, + "ga4gh-wes/runs/status" ); return RestWesProvider.handleResponse(response); } async cancelRun(runId: string): Promise { - const response = await fetch( + const response = await fetcher( `${this.baseUrl}/runs/${encodeURIComponent(runId)}/cancel`, { method: "POST", - } + }, + "ga4gh-wes/runs/cancel" ); return RestWesProvider.handleResponse(response); } diff --git a/packages/ecc-utils-design/src/index.ts b/packages/ecc-utils-design/src/index.ts index 96a7338f..4e2a755d 100644 --- a/packages/ecc-utils-design/src/index.ts +++ b/packages/ecc-utils-design/src/index.ts @@ -2,3 +2,4 @@ import "./components/index.js"; import "./events/index.js"; export * from "./components/index.js"; +export * from "./utils/apiClient.js"; diff --git a/packages/ecc-utils-design/src/utils/apiClient.ts b/packages/ecc-utils-design/src/utils/apiClient.ts new file mode 100644 index 00000000..5f446b95 --- /dev/null +++ b/packages/ecc-utils-design/src/utils/apiClient.ts @@ -0,0 +1,96 @@ +/* eslint-disable @typescript-eslint/no-explicit-any, turbo/no-undeclared-env-vars, no-console */ +export const fetcher = async ( + url: string, + options?: RequestInit, + mockPath?: string +): Promise => { + let useMock = false; + let debugMock = false; + + // Check Vite environment variable + if ( + typeof import.meta !== "undefined" && + (import.meta as any).env && + (import.meta as any).env.VITE_DEBUG_MOCK_API === "true" + ) { + debugMock = true; + } + + // Check process environment variables + if (typeof process !== "undefined" && process.env) { + if ( + process.env.VITE_DEBUG_MOCK_API === "true" || + process.env.NEXT_PUBLIC_DEBUG_MOCK_API === "true" || + process.env.DEBUG_MOCK_API === "true" + ) { + debugMock = true; + } + } + + // Check global window as fallback + if ( + typeof window !== "undefined" && + (window as any).VITE_DEBUG_MOCK_API === "true" + ) { + debugMock = true; + } + + // Check Vite environment variable + if ( + typeof import.meta !== "undefined" && + (import.meta as any).env && + (import.meta as any).env.VITE_USE_MOCK_API === "true" + ) { + useMock = true; + } + + // Check process environment variables + if (typeof process !== "undefined" && process.env) { + if ( + process.env.VITE_USE_MOCK_API === "true" || + process.env.NEXT_PUBLIC_USE_MOCK_API === "true" + ) { + useMock = true; + } + } + + // Check global window as fallback + if ( + typeof window !== "undefined" && + (window as any).VITE_USE_MOCK_API === "true" + ) { + useMock = true; + } + + if (useMock && mockPath) { + // Ensure mock path starts with slash or doesn't, we will assume it does not start with slash in the consumer calls + const normalizedMockPath = mockPath.startsWith("/") + ? mockPath + : `/${mockPath}`; + const mockUrl = `/mocks${normalizedMockPath}.json`; + if (debugMock) { + console.log( + `[API Mock] Intercepted call to ${url}. Fetching mock at ${mockUrl}` + ); + } + + try { + const mockResponse = await fetch(mockUrl); + if (mockResponse.ok) { + return mockResponse; + } + if (debugMock) { + console.warn( + `[API Mock] Enabled but failed to fetch mock file at ${mockUrl}. Status: ${mockResponse.status}. Falling back to live API.` + ); + } + } catch (e) { + if (debugMock) { + console.warn(`[API Mock] Error fetching mock file at ${mockUrl}`, e); + } + } + } + + // Fallback to real fetch if mock is not enabled, or if fetching mock failed + return fetch(url, options); +}; diff --git a/scripts/create_drs_mocks.js b/scripts/create_drs_mocks.js new file mode 100644 index 00000000..bc181ecc --- /dev/null +++ b/scripts/create_drs_mocks.js @@ -0,0 +1,23 @@ +const fs = require("fs"); +const path = require("path"); + +const mockDir = path.join(__dirname, "..", "mocks", "ga4gh-drs"); + +const files = { + "objects/get.json": + '{\n "objects": [\n {\n "id": "mock-drs-001",\n "name": "Mock DRS Object 1",\n "size": 1024,\n "created_time": "2023-01-01T00:00:00Z",\n "updated_time": "2023-01-01T00:00:00Z",\n "version": "1.0",\n "mime_type": "text/plain",\n "checksums": [\n {\n "type": "md5",\n "checksum": "d41d8cd98f00b204e9800998ecf8427e"\n }\n ]\n }\n ],\n "pagination": {\n "offset": 0,\n "limit": 10,\n "total": 1\n }\n}', + "objects/id.json": + '{\n "id": "mock-drs-001",\n "name": "Mock DRS Object 1",\n "size": 1024,\n "created_time": "2023-01-01T00:00:00Z",\n "updated_time": "2023-01-01T00:00:00Z",\n "version": "1.0",\n "mime_type": "text/plain",\n "checksums": [\n {\n "type": "md5",\n "checksum": "d41d8cd98f00b204e9800998ecf8427e"\n }\n ],\n "access_methods": [\n {\n "access_id": "access-001",\n "type": "s3",\n "region": "us-east-1"\n }\n ]\n}', + "objects/access.json": + '{\n "url": "https://example.com/mock-drs-file.txt",\n "headers": []\n}', + "service-info.json": + '{\n "id": "mock-ga4gh-drs-service",\n "name": "Mock GA4GH DRS Service",\n "type": {\n "group": "org.ga4gh",\n "artifact": "drs",\n "version": "1.1.0"\n },\n "organization": {\n "name": "Mock Org",\n "url": "https://example.com"\n },\n "version": "1.1.0"\n}', +}; + +for (const [relativePath, content] of Object.entries(files)) { + const fullPath = path.join(mockDir, relativePath); + fs.mkdirSync(path.dirname(fullPath), { recursive: true }); + fs.writeFileSync(fullPath, content); +} + +console.log("Created GA4GH DRS mock files"); diff --git a/scripts/create_ga4gh_trs_mocks.js b/scripts/create_ga4gh_trs_mocks.js new file mode 100644 index 00000000..fdd91a1f --- /dev/null +++ b/scripts/create_ga4gh_trs_mocks.js @@ -0,0 +1,35 @@ +const fs = require("fs"); +const path = require("path"); + +const mockDir = path.join(__dirname, "..", "mocks", "ga4gh-trs"); + +const files = { + "toolClasses/get.json": + '[{"id": "mock-class", "name": "Mock Class", "description": "Mock class description"}]', + "tools/get.json": + '[{"id": "mock-tool-001", "name": "Mock Tool", "organization": "Mock Org"}]', + "tools/id.json": + '{"id": "mock-tool-001", "name": "Mock Tool", "organization": "Mock Org", "description": "A mocked tool"}', + "tools/versions.json": + '[{"id": "v1", "name": "Version 1.0", "author": ["Mock Author"]}]', + "tools/version-id.json": + '{"id": "v1", "name": "Version 1.0", "author": ["Mock Author"], "is_production": true}', + "tools/files.json": '[{"path": "/mock/file.txt", "file_type": "TEST_FILE"}]', + "tools/descriptor.json": + '{"content": "mock descriptor content", "url": "/mocks/ga4gh-trs/tools/mock-content.txt"}', + "tools/descriptor-path.json": + '{"content": "mock descriptor by path", "url": "/mocks/ga4gh-trs/tools/mock-content.txt"}', + "tools/containerfile.json": + '[{"content": "FROM ubuntu:latest\\nRUN echo mock", "url": "/mocks/ga4gh-trs/tools/mock-content.txt"}]', + "tools/tests.json": + '[{"content": "mock test content", "url": "/mocks/ga4gh-trs/tools/mock-content.txt"}]', + "tools/mock-content.txt": "Mocked file content for GA4GH TRS file wrappers.", +}; + +for (const [relativePath, content] of Object.entries(files)) { + const fullPath = path.join(mockDir, relativePath); + fs.mkdirSync(path.dirname(fullPath), { recursive: true }); + fs.writeFileSync(fullPath, content); +} + +console.log("Created GA4GH TRS mock files"); diff --git a/scripts/create_trs_mocks.js b/scripts/create_trs_mocks.js new file mode 100644 index 00000000..81aa665d --- /dev/null +++ b/scripts/create_trs_mocks.js @@ -0,0 +1,40 @@ +const fs = require("fs"); +const path = require("path"); + +const mockDir = path.join(__dirname, "..", "mocks", "elixir-trs-filer"); + +const files = { + "toolClasses/get.json": + '[{"id": "mock-class", "name": "Mock Class", "description": "Mock class description"}]', + "tools/get.json": + '[{"id": "mock-tool-001", "name": "Mock Tool", "organization": "Mock Org"}]', + "tools/id.json": + '{"id": "mock-tool-001", "name": "Mock Tool", "organization": "Mock Org", "description": "A mocked tool"}', + "tools/versions.json": + '[{"id": "v1", "name": "Version 1.0", "author": ["Mock Author"]}]', + "tools/version-id.json": + '{"id": "v1", "name": "Version 1.0", "author": ["Mock Author"], "is_production": true}', + "tools/files.json": '[{"path": "/mock/file.txt", "file_type": "TEST_FILE"}]', + "tools/descriptor.json": + '{"content": "mock descriptor content", "url": "https://example.com/descriptor"}', + "tools/descriptor-path.json": + '{"content": "mock descriptor by path", "url": "https://example.com/descriptor"}', + "tools/containerfile.json": + '[{"content": "FROM ubuntu:latest\\nRUN echo mock", "url": "https://example.com/containerfile"}]', + "tools/tests.json": + '[{"content": "mock test content", "url": "https://example.com/test"}]', + "tools/post.json": '{"id": "mock-tool-new-post"}', + "tools/put.json": '{"id": "mock-tool-put-updated"}', + "tools/versions-post.json": '{"id": "mock-version-post"}', + "tools/versions-put.json": '{"id": "mock-version-put"}', + "toolClasses/post.json": '{"id": "mock-toolclass-post"}', + "toolClasses/put.json": '{"id": "mock-toolclass-put"}', +}; + +for (const [relativePath, content] of Object.entries(files)) { + const fullPath = path.join(mockDir, relativePath); + fs.mkdirSync(path.dirname(fullPath), { recursive: true }); + fs.writeFileSync(fullPath, content); +} + +console.log("Created TRS mock files");