@scality/data-browser-library 1.0.8 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/components/__tests__/BucketCorsPage.test.js +67 -9
  2. package/dist/components/__tests__/BucketDetails.test.js +1 -0
  3. package/dist/components/__tests__/BucketLifecycleFormPage.test.js +16 -11
  4. package/dist/components/__tests__/BucketNotificationFormPage.test.js +45 -0
  5. package/dist/components/__tests__/BucketOverview.test.js +92 -2
  6. package/dist/components/__tests__/BucketPolicyPage.test.js +70 -51
  7. package/dist/components/__tests__/BucketReplicationFormPage.test.js +18 -24
  8. package/dist/components/__tests__/ObjectList.test.js +43 -2
  9. package/dist/components/buckets/BucketConfigEditButton.d.ts +2 -0
  10. package/dist/components/buckets/BucketConfigEditButton.js +9 -3
  11. package/dist/components/buckets/BucketCorsPage.js +57 -20
  12. package/dist/components/buckets/BucketDetails.js +27 -2
  13. package/dist/components/buckets/BucketLifecycleFormPage.js +310 -270
  14. package/dist/components/buckets/BucketOverview.js +21 -18
  15. package/dist/components/buckets/BucketPolicyPage.js +119 -83
  16. package/dist/components/buckets/BucketReplicationFormPage.js +39 -29
  17. package/dist/components/buckets/BucketVersioning.js +16 -10
  18. package/dist/components/buckets/__tests__/BucketVersioning.test.js +76 -23
  19. package/dist/components/buckets/notifications/BucketNotificationFormPage.js +13 -5
  20. package/dist/components/objects/ObjectDetails/__tests__/ObjectDetails.test.js +3 -3
  21. package/dist/components/objects/ObjectList.js +22 -25
  22. package/dist/components/objects/ObjectLock/EditRetentionButton.js +2 -2
  23. package/dist/hooks/factories/useCreateS3InfiniteQueryHook.js +2 -0
  24. package/dist/hooks/index.d.ts +1 -1
  25. package/dist/hooks/objectOperations.d.ts +3 -3
  26. package/dist/hooks/objectOperations.js +3 -3
  27. package/dist/hooks/useBucketConfigEditor.d.ts +4 -4
  28. package/dist/hooks/useBucketConfigEditor.js +16 -31
  29. package/dist/test/mocks/esmOnlyModules.js +4 -0
  30. package/dist/types/index.d.ts +0 -1
  31. package/dist/utils/__tests__/proxyMiddleware.test.js +34 -0
  32. package/dist/utils/proxyMiddleware.js +2 -0
  33. package/package.json +4 -4
  34. package/dist/components/Editor.d.ts +0 -12
  35. package/dist/components/Editor.js +0 -28
  36. package/dist/types/monaco.d.ts +0 -13
  37. package/dist/types/monaco.js +0 -0
@@ -1,9 +1,11 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { act, fireEvent, render, renderHook, screen, waitFor } from "@testing-library/react";
3
+ import { HttpResponse, http } from "msw";
3
4
  import { MemoryRouter } from "react-router";
4
5
  import { DataBrowserUICustomizationProvider } from "../../contexts/DataBrowserUICustomizationContext.js";
5
- import { useListObjectVersions, useListObjects } from "../../hooks/index.js";
6
- import { createTestWrapper, mockOffsetSize, setupMswServer } from "../../test/testUtils.js";
6
+ import { useListObjectVersions, useListObjects, useSearchObjects } from "../../hooks/index.js";
7
+ import { getS3BaseUrl } from "../../test/msw/utils.js";
8
+ import { createTestWrapper, mockOffsetSize, overrideHandlers, setupMswServer } from "../../test/testUtils.js";
7
9
  import { ObjectList } from "../objects/ObjectList.js";
8
10
  import * as __rspack_external__hooks_useFeatures_js_a6a84786 from "../../hooks/useFeatures.js";
9
11
  setupMswServer();
@@ -391,6 +393,45 @@ describe('ObjectList', ()=>{
391
393
  });
392
394
  jest.useRealTimers();
393
395
  });
396
+ it('passes the search query parameter to the S3 request when metadata search is active', async ()=>{
397
+ let capturedSearchParam = null;
398
+ overrideHandlers(http.get(`${getS3BaseUrl()}/:bucketName`, async ({ request })=>{
399
+ const url = new URL(request.url);
400
+ if ('2' !== url.searchParams.get('list-type')) return;
401
+ capturedSearchParam = url.searchParams.get('search');
402
+ return HttpResponse.xml(`<?xml version="1.0" encoding="UTF-8"?>
403
+ <ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
404
+ <Name>test-bucket</Name>
405
+ <Prefix></Prefix>
406
+ <Delimiter>/</Delimiter>
407
+ <MaxKeys>100</MaxKeys>
408
+ <IsTruncated>false</IsTruncated>
409
+ <Contents>
410
+ <Key>matched-file.pdf</Key>
411
+ <LastModified>2023-10-12T17:50:00.000Z</LastModified>
412
+ <ETag>&quot;abc123&quot;</ETag>
413
+ <Size>2048</Size>
414
+ <StorageClass>STANDARD</StorageClass>
415
+ </Contents>
416
+ </ListBucketResult>`, {
417
+ status: 200
418
+ });
419
+ }));
420
+ const searchQuery = 'key like /pdf$/';
421
+ const { result } = renderHook(()=>useSearchObjects({
422
+ Bucket: 'test-bucket',
423
+ Prefix: '',
424
+ MaxKeys: 100,
425
+ Delimiter: '/',
426
+ Query: searchQuery
427
+ }), {
428
+ wrapper: createTestWrapper()
429
+ });
430
+ await waitFor(()=>{
431
+ expect(result.current.isSuccess).toBe(true);
432
+ });
433
+ expect(capturedSearchParam).toBe(searchQuery);
434
+ });
394
435
  });
395
436
  describe('Multi-Selection', ()=>{
396
437
  it('integrates with MultiSelectableContent', async ()=>{
@@ -3,6 +3,8 @@ interface BucketConfigEditButtonProps {
3
3
  isLoading?: boolean;
4
4
  onEdit: () => void;
5
5
  configName: string;
6
+ disabled?: boolean;
7
+ tooltipOverlay?: string;
6
8
  }
7
9
  export declare const BucketConfigEditButton: React.FC<BucketConfigEditButtonProps>;
8
10
  export {};
@@ -1,7 +1,7 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { Icon, spacing } from "@scality/core-ui";
3
3
  import { Box, Button } from "@scality/core-ui/dist/next";
4
- const BucketConfigEditButton = ({ hasConfig, isLoading, onEdit, configName })=>{
4
+ const BucketConfigEditButton = ({ hasConfig, isLoading, onEdit, configName, disabled, tooltipOverlay })=>{
5
5
  const action = hasConfig ? 'Edit' : 'Create';
6
6
  return /*#__PURE__*/ jsx(Box, {
7
7
  display: "flex",
@@ -12,10 +12,16 @@ const BucketConfigEditButton = ({ hasConfig, isLoading, onEdit, configName })=>{
12
12
  variant: "outline",
13
13
  label: action,
14
14
  icon: /*#__PURE__*/ jsx(Icon, {
15
- name: hasConfig ? 'Pen' : 'Create-add'
15
+ name: hasConfig ? 'Pencil' : 'Create-add'
16
16
  }),
17
17
  onClick: onEdit,
18
- "aria-label": `${action} ${configName}`
18
+ "aria-label": `${action} ${configName}`,
19
+ disabled: disabled,
20
+ ...tooltipOverlay && {
21
+ tooltip: {
22
+ overlay: tooltipOverlay
23
+ }
24
+ }
19
25
  })
20
26
  });
21
27
  };
@@ -1,18 +1,20 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { Form, FormGroup, FormSection, Icon, Loader, Stack, Text, useToast } from "@scality/core-ui";
3
- import { Box, Button, CopyButton } from "@scality/core-ui/dist/next";
4
- import { useCallback, useMemo } from "react";
3
+ import { Box, Button, CopyButton, Editor } from "@scality/core-ui/dist/next";
4
+ import { useCallback, useEffect, useMemo } from "react";
5
5
  import { Controller } from "react-hook-form";
6
6
  import { useParams } from "react-router";
7
- import { useBucketConfigEditor, useDeleteBucketCors, useGetBucketCors, useSetBucketCors } from "../../hooks/index.js";
8
- import { Editor } from "../Editor.js";
7
+ import { useBucketConfigEditor, useDeleteBucketCors, useGetBucketCors, useISVBucketStatus, useSetBucketCors } from "../../hooks/index.js";
8
+ import { useDataBrowserNavigate } from "../../hooks/useDataBrowserNavigate.js";
9
+ import { isNotFoundError } from "../../utils/errorHandling.js";
9
10
  const DEFAULT_CORS_TEMPLATE = `[
10
11
  {
11
12
  "AllowedHeaders": [
12
13
  "Authorization"
13
14
  ],
14
15
  "AllowedMethods": [
15
- "GET"
16
+ "GET",
17
+ "PUT"
16
18
  ],
17
19
  "AllowedOrigins": [
18
20
  "*"
@@ -47,22 +49,35 @@ const validateCorsConfig = (content)=>{
47
49
  const BucketCorsPage = ()=>{
48
50
  const { bucketName } = useParams();
49
51
  const { showToast } = useToast();
52
+ const { isISVManaged, isLoading: isISVLoading } = useISVBucketStatus(bucketName);
53
+ const navigate = useDataBrowserNavigate();
54
+ useEffect(()=>{
55
+ if (isISVManaged) navigate(`/buckets/${bucketName}`, {
56
+ replace: true
57
+ });
58
+ }, [
59
+ isISVManaged,
60
+ bucketName,
61
+ navigate
62
+ ]);
63
+ const shouldFetchData = !isISVManaged && !isISVLoading;
50
64
  const { data: corsData, status: corsStatus, error: corsError } = useGetBucketCors({
51
65
  Bucket: bucketName
66
+ }, {
67
+ enabled: shouldFetchData
52
68
  });
53
69
  const initialContent = useMemo(()=>{
70
+ if (isNotFoundError(corsError)) return;
54
71
  if (corsData?.CORSRules && corsData.CORSRules.length > 0) return JSON.stringify(corsData.CORSRules);
55
72
  }, [
56
- corsData
73
+ corsData,
74
+ corsError
57
75
  ]);
58
- const { form, content, isCreateMode, isValidFormat, handleBeforeMount, navigateToBucket } = useBucketConfigEditor({
76
+ const { form, content, isCreateMode, isValidFormat, navigateToBucket, loadTemplate } = useBucketConfigEditor({
59
77
  bucketName: bucketName,
60
78
  initialContent,
61
79
  defaultTemplate: DEFAULT_CORS_TEMPLATE,
62
80
  isLoading: 'pending' === corsStatus,
63
- notFoundErrorNames: [
64
- 'NoSuchCORSConfiguration'
65
- ],
66
81
  errorInstance: corsError,
67
82
  validate: validateCorsConfig
68
83
  });
@@ -121,7 +136,8 @@ const BucketCorsPage = ()=>{
121
136
  setError
122
137
  ]);
123
138
  const isPending = isSaving || isDeleting;
124
- const hasUnexpectedError = corsError && 'NoSuchCORSConfiguration' !== corsError.name;
139
+ if (isISVManaged || isISVLoading) return null;
140
+ const hasUnexpectedError = corsError && !isNotFoundError(corsError);
125
141
  if ('pending' === corsStatus) return /*#__PURE__*/ jsx(Loader, {
126
142
  centered: true,
127
143
  size: "massive",
@@ -200,6 +216,11 @@ const BucketCorsPage = ()=>{
200
216
  direction: "vertical",
201
217
  error: errors.content?.message,
202
218
  content: /*#__PURE__*/ jsxs(Stack, {
219
+ direction: "horizontal",
220
+ gap: "r16",
221
+ style: {
222
+ alignItems: 'flex-start'
223
+ },
203
224
  children: [
204
225
  /*#__PURE__*/ jsx(Controller, {
205
226
  control: control,
@@ -212,17 +233,33 @@ const BucketCorsPage = ()=>{
212
233
  },
213
234
  language: "json",
214
235
  height: "60vh",
215
- width: "33rem",
216
- beforeMount: handleBeforeMount
236
+ width: "33rem"
217
237
  })
218
238
  }),
219
- /*#__PURE__*/ jsx(Box, {
220
- alignSelf: "baseline",
221
- children: /*#__PURE__*/ jsx(CopyButton, {
222
- textToCopy: content,
223
- label: "CORS",
224
- variant: "outline"
225
- })
239
+ /*#__PURE__*/ jsxs(Stack, {
240
+ direction: "vertical",
241
+ gap: "r8",
242
+ children: [
243
+ /*#__PURE__*/ jsx(CopyButton, {
244
+ textToCopy: content,
245
+ label: "CORS",
246
+ variant: "outline"
247
+ }),
248
+ isCreateMode && !content && /*#__PURE__*/ jsx(Text, {
249
+ variant: "Smaller",
250
+ color: "textSecondary",
251
+ children: "Empty CORS rule"
252
+ }),
253
+ isCreateMode && !content && /*#__PURE__*/ jsx(Button, {
254
+ variant: "outline",
255
+ label: "Load a template",
256
+ onClick: loadTemplate,
257
+ type: "button",
258
+ style: {
259
+ minWidth: '9rem'
260
+ }
261
+ })
262
+ ]
226
263
  })
227
264
  ]
228
265
  })
@@ -1,7 +1,9 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { useQueryClient } from "@tanstack/react-query";
2
3
  import { Tabs } from "@scality/core-ui/dist/next";
3
4
  import { createContext, memo, useCallback, useContext, useMemo } from "react";
4
5
  import { useParams } from "react-router";
6
+ import { useDataBrowserContext } from "../providers/DataBrowserProvider.js";
5
7
  import { useDataBrowserUICustomization } from "../../contexts/DataBrowserUICustomizationContext.js";
6
8
  import { useGetBucketLifecycle, useGetBucketNotification, useGetBucketReplication } from "../../hooks/bucketConfiguration.js";
7
9
  import { useDataBrowserNavigate } from "../../hooks/useDataBrowserNavigate.js";
@@ -30,12 +32,35 @@ const ExtraSectionContent = /*#__PURE__*/ memo(({ render })=>/*#__PURE__*/ jsx(F
30
32
  children: render()
31
33
  }));
32
34
  ExtraSectionContent.displayName = 'BucketDetails.ExtraSectionContent';
35
+ const LIST_BUCKETS_QUERY_KEY = 'ListBuckets';
33
36
  const OverviewTab = /*#__PURE__*/ memo(()=>{
34
37
  const { bucketName, navigate } = useBucketDetailsContext();
35
38
  const { extraBucketOverviewSections } = useDataBrowserUICustomization();
39
+ const queryClient = useQueryClient();
40
+ const { s3ConfigIdentifier } = useDataBrowserContext();
41
+ const handleDeleteSuccess = useCallback(()=>{
42
+ queryClient.setQueriesData({
43
+ queryKey: [
44
+ s3ConfigIdentifier,
45
+ LIST_BUCKETS_QUERY_KEY
46
+ ]
47
+ }, (prev)=>prev?.Buckets ? {
48
+ ...prev,
49
+ Buckets: prev.Buckets.filter((b)=>b.Name !== bucketName)
50
+ } : prev);
51
+ navigate('/buckets');
52
+ }, [
53
+ queryClient,
54
+ s3ConfigIdentifier,
55
+ navigate,
56
+ bucketName
57
+ ]);
36
58
  const renderDeleteButton = useCallback((name)=>/*#__PURE__*/ jsx(DeleteBucketButton, {
37
- bucketName: name
38
- }), []);
59
+ bucketName: name,
60
+ onDeleteSuccess: handleDeleteSuccess
61
+ }), [
62
+ handleDeleteSuccess
63
+ ]);
39
64
  const renderEmptyButton = useCallback((name)=>/*#__PURE__*/ jsx(EmptyBucketButton, {
40
65
  bucketName: name
41
66
  }), []);