Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

UPLOAD-1695 Show upload status of file in info #475

Merged
merged 51 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
c0d310a
reading the upload-status file to determine the current status of the…
thetif Sep 5, 2024
f192919
Merge branch 'main' of github.com:CDCgov/data-exchange-upload into UP…
thetif Sep 5, 2024
ef10787
changed the file upload status to use the fileinfo to speed up the check
thetif Sep 5, 2024
236261d
added viewport definition to index.html
thetif Sep 9, 2024
14f58ec
pulled in the latest from main
thetif Sep 9, 2024
1b2c3dc
fixed merge issue
thetif Sep 9, 2024
a2d4c61
trying to remove unnecessary changes to see if that resolves upload v…
thetif Sep 10, 2024
54168c3
pulled in branch code
thetif Sep 10, 2024
5e2c6c6
Merge branch 'main' of github.com:CDCgov/data-exchange-upload into UP…
thetif Sep 10, 2024
479f644
pushing the info result to the upload template
thetif Sep 10, 2024
bcfb856
header for status
Sun-Mountain Sep 10, 2024
903b6f9
merge conflicts resolved
Sun-Mountain Sep 10, 2024
3c08778
show info
Sun-Mountain Sep 11, 2024
1a9ac79
rewriting the tus script
thetif Sep 11, 2024
19751eb
Merge branch 'UPLOAD-1695/file-upload-status' of github.com:CDCgov/da…
thetif Sep 11, 2024
01d5756
messing with styles
thetif Sep 11, 2024
af7313d
changes to format of file info section on upload page
thetif Sep 11, 2024
3d0c27f
fixed lastChunkReceived bug in the upload status code
thetif Sep 11, 2024
d0bf314
added ability to update upload status and last chunk received because…
thetif Sep 11, 2024
0364ad4
ui changes
Sun-Mountain Sep 12, 2024
d3c68cc
fix merge conflicts
Sun-Mountain Sep 12, 2024
9d4f40a
fixing date locality issue
thetif Sep 12, 2024
2696daf
Merge branch 'UPLOAD-1695/file-upload-status' of github.com:CDCgov/da…
thetif Sep 12, 2024
6cea63d
updated order
Sun-Mountain Sep 12, 2024
ffb4992
tweaks
thetif Sep 12, 2024
40be3de
add buttons and styling
Sun-Mountain Sep 12, 2024
8fd0adb
Merge branch 'UPLOAD-1695/file-upload-status' of github.com:CDCgov/da…
Sun-Mountain Sep 12, 2024
6779a9f
tweaks
thetif Sep 12, 2024
8ae616b
add upload new file to nav
Sun-Mountain Sep 12, 2024
fef47c5
link styling
Sun-Mountain Sep 12, 2024
28e9b11
Merge branch 'UPLOAD-1695/file-upload-status' of github.com:CDCgov/da…
thetif Sep 12, 2024
965f4ae
merge parent branch
Sun-Mountain Sep 12, 2024
f8cf997
tweaks
thetif Sep 12, 2024
e4eeb66
Updating playwright tests to handle recent changes and start testing …
cyber-decker Sep 12, 2024
2919dac
ui tweaks
thetif Sep 12, 2024
15683c5
Merge branch 'main' of github.com:CDCgov/data-exchange-upload into UP…
thetif Sep 13, 2024
36981a0
removing unneeded code from the html and css, cleaning up some minor …
thetif Sep 13, 2024
515be26
fixed bug in js, change some class names
thetif Sep 13, 2024
b077a60
responding to comments in PR
thetif Sep 16, 2024
4be7f70
added pause/resume button
thetif Sep 16, 2024
29ffe4b
Merge branch 'main' of github.com:CDCgov/data-exchange-upload into UP…
thetif Sep 16, 2024
3de04cc
added upload_status to the swagger doc
thetif Sep 16, 2024
737be08
Merge branch 'main' of github.com:CDCgov/data-exchange-upload into UP…
thetif Sep 16, 2024
ea9c6e5
Updating tests to fix latest changes and adding page checks for the s…
cyber-decker Sep 17, 2024
8522e0b
update orange color for warning
Sun-Mountain Sep 17, 2024
8dc9247
addressing issues from PR
thetif Sep 17, 2024
0c56186
Merge branch 'UPLOAD-1695/file-upload-status' of github.com:CDCgov/da…
thetif Sep 17, 2024
8e405a3
added clarity to the page refresh
thetif Sep 17, 2024
d0d97ab
fixes for accessibility issues
thetif Sep 17, 2024
7623f69
Updating a couple of tests for UI accessibility changes
cyber-decker Sep 17, 2024
b42bec9
changed the refresh back to location.reload for security, updated css…
thetif Sep 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
411 changes: 222 additions & 189 deletions docs/openapi.yml

Large diffs are not rendered by default.

22 changes: 10 additions & 12 deletions tests/smoke/playwright/test/upload-test-accessibility.spec.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,30 @@
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';


test.describe.configure({ mode: 'parallel' });
test.describe('Upload User Interface', () => {

const axeRuleTags = ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"];
const axeRuleTags = ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"];

test('Checks accessiblity for the upload landing page', async ({ page }, testInfo) => {
test.describe('Upload Landing Page', () => {
test('has accessible features when loaded', async ({ page }, testInfo) => {
await page.goto(`/`)
const results = await new AxeBuilder({ page })
.withTags(axeRuleTags)
.analyze();

expect(results.violations).toEqual([]);
});
});

test.describe('Upload Manifest Page', () => {
[
{ dataStream: "celr", route: "csv" },
{ dataStream: "celr", route: "hl7v2" },
{ dataStream: "covid", route: "all-monthly-vaccination-csv" },
{ dataStream: "covid", route: "bridge-vaccination-csv" },
{ dataStream: "daart", route: "hl7" },
{ dataStream: "dex", route: "hl7-hl7ingress" },
{ dataStream: "dextesting", route: "testevent1" },
{ dataStream: "ehdi", route: "csv" },
{ dataStream: "eicr", route: "fhir" },
{ dataStream: "h5", route: "influenza-vaccination-csv" },
{ dataStream: "influenza", route: "vaccination-csv" },
{ dataStream: "ndlp", route: "aplhistoricaldata" },
{ dataStream: "ndlp", route: "covidallmonthlyvaccination" },
{ dataStream: "ndlp", route: "covidbridgevaccination" },
{ dataStream: "ndlp", route: "influenzavaccination" },
Expand All @@ -45,14 +42,15 @@ test.describe('Upload User Interface', () => {
expect(results.violations).toEqual([]);
})
});
});

test(`Checks accessibliity for the upload page for the daart/hl7 manifest`, async ({ page }) => {
await page.goto(`/manifest?data_stream=daart&data_stream_route=hl7`);
test.describe('File Upload Page', () => {
test(`Checks accessibliity for the upload page for the dextesting/testevent1 manifest`, async ({ page }) => {
await page.goto(`/manifest?data_stream=dextesting&data_stream_route=testevent1`);
await page.getByLabel('Sender Id').fill('Sender123');
await page.getByLabel('Data Producer Id').fill('Producer123');
await page.getByLabel('Jurisdiction').fill('Jurisdiction123');
await page.getByLabel('Received Filename').fill('small-test-file');
await page.getByLabel('Original File Timestamp').fill('Timestamp123');
await page.getByRole('button', { name: /next/i }).click();
await expect(page).toHaveURL(/status/)
const results = await new AxeBuilder({ page })
Expand Down
6 changes: 3 additions & 3 deletions tests/smoke/playwright/test/upload-test-e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ test.describe("Upload End to End Tests", () => {
await page.goto(`/`);
await page.getByLabel('Data Stream', {exact: true}).fill(dataStream);
await page.getByLabel('Data Stream Route').fill(route);
await page.getByRole('button', {name: /submit/i }).click();
await page.getByRole('button', {name: /next/i }).click();

await page.getByLabel('Sender Id').fill('Sender123')
await page.getByLabel('Data Producer Id').fill('Producer123')
Expand All @@ -20,9 +20,9 @@ test.describe("Upload End to End Tests", () => {

await page.locator('input[type="file"]').click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles('../upload-files/10KB-test-file');
await fileChooser.setFiles('../upload-files/10KB-test-file');

await page.getByText('Download 10KB-test-file')
await expect(page.getByText('Upload Status: Complete')).toBeVisible();
})
})

123 changes: 117 additions & 6 deletions tests/smoke/playwright/test/upload-test-pages.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,29 @@ import { expect, test } from '@playwright/test';

test.describe.configure({ mode: 'parallel' });

test.describe("Upload Landing Page", () => {
test("has the expected elements to start a file upload process", async ({page}) => {
await page.goto(`/`);
const nav = page.getByRole('navigation')
await expect(nav.getByRole("link").and(nav.getByText('Skip to main content Upload'))).toBeHidden()
await expect(nav.getByRole("link").and(nav.getByText('Upload'))).toBeVisible()
await expect(page.getByRole('heading', { level: 1 })).toHaveText('Welcome to DEX Upload')
await expect(page.getByRole('heading', { level: 2 })).toHaveText('Start the upload process by entering a data stream and route.')
await expect(page.getByRole("textbox", { name: "Data Stream", exact: true })).toBeVisible()
await expect(page.getByRole("textbox", { name: "Data Stream Route", exact: true })).toBeVisible()
await expect(page.getByRole("button", {name: "Next"})).toBeVisible()
})
});

[
{ dataStream: "celr", route: "csv" },
{ dataStream: "celr", route: "hl7v2" },
{ dataStream: "covid", route: "all-monthly-vaccination-csv" },
{ dataStream: "covid", route: "bridge-vaccination-csv" },
{ dataStream: "daart", route: "hl7" },
{ dataStream: "dex", route: "hl7-hl7ingress" },
{ dataStream: "dextesting", route: "testevent1" },
{ dataStream: "ehdi", route: "csv" },
{ dataStream: "eicr", route: "fhir" },
{ dataStream: "h5", route: "influenza-vaccination-csv" },
{ dataStream: "influenza", route: "vaccination-csv" },
{ dataStream: "ndlp", route: "aplhistoricaldata" },
{ dataStream: "ndlp", route: "covidallmonthlyvaccination" },
{ dataStream: "ndlp", route: "covidbridgevaccination" },
{ dataStream: "ndlp", route: "influenzavaccination" },
Expand All @@ -23,9 +34,12 @@ test.describe.configure({ mode: 'parallel' });
{ dataStream: "routine", route: "immunization-other" },
{ dataStream: "rsv", route: "prevention-csv" },
].forEach(({ dataStream, route }) => {
test.describe("Manifest UI endpoint", () => {
test(`Data stream: ${dataStream} / Route: ${route} displays the appropriate UI fields`, async ({ page }) => {
test.describe("Upload Manifest Page", () => {
test(`has the expected metadata elements for Data stream: ${dataStream} / Route: ${route}`, async ({ page }) => {
await page.goto(`/manifest?data_stream=${dataStream}&data_stream_route=${route}`);
const nav = page.locator('nav')
await expect(nav.getByRole("link").and(nav.getByText('Skip to main content'))).toBeHidden()
await expect(nav.getByRole("link").and(nav.getByText('Upload'))).toBeVisible()
const title = page.locator('h1');
await expect(title).toHaveText('Please fill in the sender manifest form for your file');
// TODO: Add more assertions on individual manifest page elements
Expand All @@ -34,3 +48,100 @@ test.describe.configure({ mode: 'parallel' });
})
})
});

test.describe("File Uploader Page", () => {
test("has the expected elements to prepare to upload a file", async ({ page, baseURL }) => {
const apiURL = baseURL.replace('8081', '8080')
const dataStream = 'dextesting';
const route = 'testevent1';

await page.goto(`/manifest?data_stream=${dataStream}&data_stream_route=${route}`);

await page.getByLabel('Sender Id').fill('Sender123')
await page.getByLabel('Data Producer Id').fill('Producer123')
await page.getByLabel('Jurisdiction').fill('Jurisdiction123')
await page.getByLabel('Received Filename').fill('small-test-file')
await page.getByRole('button', { name: /next/i }).click();

await expect(page.getByRole('heading', { level: 1, includeHidden: false }).nth(0)).toHaveText('File Uploader')
const uploadEndpoint = page.getByRole('textbox', { name: 'Upload Endpoint' });
// not the greatest way to interpret the endpoint value here, but this will have to work for now...
await expect(uploadEndpoint).toHaveValue(`${apiURL}/files/`)

const chunkSize = page.getByLabel('Chunk size (bytes)');
const chunkSizeLabel = page.locator('label', { hasText: 'Chunk size (bytes)' })
await expect(chunkSizeLabel).toContainText('Note: Chunksize should be set on the client for uploading files of large size (1GB or over).')
await expect(chunkSize).toHaveValue('40000000')

const parallelUploadRequests = page.getByRole('spinbutton', { name: 'Parallel upload requests' })
await expect(parallelUploadRequests).toHaveValue('1')

const browseFilesButton = page.getByLabel('Browse Files')
await expect(browseFilesButton).toHaveAttribute("onclick", "files.click()")
})
})

test.describe("Upload Status Page", () => {

test("has the expected elements to display upload status", async ({ page, baseURL }) => {
const apiURL = baseURL.replace('8081', '8080')
const dataStream = 'dextesting';
const route = 'testevent1';
const expectedFileName = 'small-test-file'
const expectedSender = 'Sender123'
const expectedDataProducer = 'Producer123'
const expectedJurisdiction = 'Jurisdiction123'

await page.goto(`/manifest?data_stream=${dataStream}&data_stream_route=${route}`);

await page.getByLabel('Sender Id').fill(expectedSender)
await page.getByLabel('Data Producer Id').fill(expectedDataProducer)
await page.getByLabel('Jurisdiction').fill(expectedJurisdiction)
await page.getByLabel('Received Filename').fill(expectedFileName)
await page.getByRole('button', {name: /next/i }).click();

const fileChooserPromise = page.waitForEvent('filechooser');
const uploadId = page.url().split('/').slice(-1)[0]

const uploadHeadResponsePromise = page.waitForResponse(response =>
response.url() === `${apiURL}/files/${uploadId}` && response.status() === 200
&& response.request().method() === 'HEAD'
);

const uploadPatchResponsePromise = page.waitForResponse(response =>
response.url() === `${apiURL}/files/${uploadId}` && response.status() === 204
&& response.request().method() === 'PATCH'
);

await page.getByRole('button', {name: /Metadata JSON Object/}).click();
// await page.getByRole('button', {name: 'Browse Files'}).click(); // want to be able to do this instead, accessible name is wrong?

const fileChooser = await fileChooserPromise;
await fileChooser.setFiles('../upload-files/10KB-test-file');

await expect((await uploadPatchResponsePromise).ok()).toBeTruthy()
await expect((await uploadHeadResponsePromise).ok()).toBeTruthy()

await page.reload();

const fileHeaderContainer= page.locator('.file-header-container')
await expect(fileHeaderContainer.getByRole('heading', { level: 1 }).nth(0)).toHaveText(expectedFileName)
await expect(fileHeaderContainer.getByRole('heading', { level: 1 }).nth(1)).toHaveText("Upload Status: Complete")
await expect(fileHeaderContainer).toContainText(`ID: ${uploadId}`)

const fileDeliveriesContainer = page.locator('.file-deliveries-container');
await expect(fileDeliveriesContainer.getByRole('heading', { level: 2 }).nth(0)).toHaveText('Delivery Status')
await expect(fileDeliveriesContainer.getByRole('heading', { level: 2 }).nth(1)).toHaveText('EDAV')
await expect(fileDeliveriesContainer.getByRole('heading', {level: 3})).toHaveText('Delivery Status: SUCCESS')
await expect(fileDeliveriesContainer).toContainText(`Location: uploads/edav/${uploadId}`)

const uploadDetailsContainer = page.locator('.file-details-container')
await expect(uploadDetailsContainer.getByRole('heading', { level: 2 })).toHaveText('Upload Details')
await expect(uploadDetailsContainer).toContainText(`File Size: 10240 bytes`)
await expect(uploadDetailsContainer).toContainText(`Sender ID: ${expectedSender.toUpperCase()}`)
await expect(uploadDetailsContainer).toContainText(`Producer ID: ${expectedDataProducer.toUpperCase()}`)
await expect(uploadDetailsContainer).toContainText(`Stream ID: ${dataStream.toUpperCase()}`)
await expect(uploadDetailsContainer).toContainText(`Stream Route: ${route.toUpperCase()}`)
await expect(uploadDetailsContainer).toContainText(`Jurisdiction: ${expectedJurisdiction.toUpperCase()}`)
})
})
2 changes: 1 addition & 1 deletion tests/smoke/playwright/test/upload-test.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ dotenv.config({ path: '../../.env' });


// Use test.describe to group your tests and use hooks like beforeAll
test.describe('File Upload and Trace Response Flow', () => {
test.describe.skip('File Upload and Trace Response Flow', () => {
let uploadId;
let accessToken: string;
let psApiUrl: string;
Expand Down
5 changes: 3 additions & 2 deletions upload-server/cmd/cli/datastore.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package cli
import (
"context"
"fmt"
"github.com/cdcgov/data-exchange-upload/upload-server/internal/stores3"
"github.com/tus/tusd/v2/pkg/s3store"
"os"
"path/filepath"

"github.com/cdcgov/data-exchange-upload/upload-server/internal/stores3"
"github.com/tus/tusd/v2/pkg/s3store"

"github.com/cdcgov/data-exchange-upload/upload-server/internal/appconfig"
"github.com/cdcgov/data-exchange-upload/upload-server/internal/handlertusd"
"github.com/cdcgov/data-exchange-upload/upload-server/internal/health"
Expand Down
35 changes: 20 additions & 15 deletions upload-server/cmd/cli/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import (
"encoding/json"
"errors"
"fmt"
"net/http"

"github.com/cdcgov/data-exchange-upload/upload-server/internal/stores3"
"github.com/cdcgov/data-exchange-upload/upload-server/pkg/azureinspector"
"github.com/cdcgov/data-exchange-upload/upload-server/pkg/fileinspector"
"github.com/cdcgov/data-exchange-upload/upload-server/pkg/s3inspector"
"net/http"

"github.com/cdcgov/data-exchange-upload/upload-server/internal/appconfig"
"github.com/cdcgov/data-exchange-upload/upload-server/internal/storeaz"
Expand All @@ -35,11 +36,6 @@ func (ih *InfoHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
return
}

response := &info.InfoResponse{
Manifest: fileInfo,
Deliveries: []info.FileDeliveryStatus{},
}

uploadedFileInfo, err := ih.inspector.InspectUploadedFile(r.Context(), id)
if err != nil {
// skip not found errors to handle deferred uploads.
Expand All @@ -49,17 +45,30 @@ func (ih *InfoHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
}
}

uploadStatus, err := ih.statusInspector.InspectFileUploadStatus(r.Context(), id)
if err != nil {
// skip not found errors to handle deferred uploads.
if !errors.Is(err, info.ErrNotFound) {
http.Error(rw, fmt.Sprintf("error getting file upload status. Manifest: %#v", fileInfo), getStatusFromError(err))
return
}
}

deliveries, err := ih.statusInspector.InspectFileDeliveryStatus(r.Context(), id)
if err != nil {
// skip not found errors to handle deferred uploads.
if !errors.Is(err, info.ErrNotFound) {
http.Error(rw, fmt.Sprintf("error getting file status. Manifest: %#v", fileInfo), getStatusFromError(err))
http.Error(rw, fmt.Sprintf("error getting file delivery status. Manifest: %#v", fileInfo), getStatusFromError(err))
return
}
}

response.FileInfo = uploadedFileInfo
response.Deliveries = deliveries
response := &info.InfoResponse{
Manifest: fileInfo,
FileInfo: uploadedFileInfo,
UploadStatus: uploadStatus,
Deliveries: deliveries,
}

rw.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(rw)
Expand All @@ -85,16 +94,12 @@ func createInspector(ctx context.Context, appConfig *appconfig.AppConfig) (Uploa
return azureinspector.NewAzureUploadInspector(containerClient, appConfig.TusUploadPrefix), nil
}
if appConfig.S3Connection != nil {
s3Client, err := stores3.New(ctx, appConfig.S3Connection)
containerClient, err := stores3.New(ctx, appConfig.S3Connection)
thetif marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}

return &s3inspector.S3UploadInspector{
Client: s3Client,
BucketName: appConfig.S3Connection.BucketName,
TusPrefix: appConfig.TusUploadPrefix,
}, nil
return s3inspector.NewS3UploadInspector(containerClient, appConfig.S3Connection.BucketName, appConfig.TusUploadPrefix), nil
}
if appConfig.LocalFolderUploadsTus != "" {
return fileinspector.NewFileSystemUploadInspector(appConfig.LocalFolderUploadsTus, appConfig.TusUploadPrefix), nil
Expand Down
2 changes: 2 additions & 0 deletions upload-server/cmd/cli/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package cli

import (
"context"

"github.com/cdcgov/data-exchange-upload/upload-server/pkg/info"
)

type UploadStatusInspector interface {
InspectFileDeliveryStatus(ctx context.Context, id string) ([]info.FileDeliveryStatus, error)
InspectFileUploadStatus(ctx context.Context, id string) (info.FileUploadStatus, error)
}
7 changes: 4 additions & 3 deletions upload-server/internal/delivery/deliver.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ import (
"bytes"
"context"
"fmt"
"github.com/cdcgov/data-exchange-upload/upload-server/internal/metadata"
"github.com/cdcgov/data-exchange-upload/upload-server/internal/stores3"
metadataPkg "github.com/cdcgov/data-exchange-upload/upload-server/pkg/metadata"
"io"
"log/slog"
"os"
Expand All @@ -16,6 +13,10 @@ import (
"text/template"
"time"

"github.com/cdcgov/data-exchange-upload/upload-server/internal/metadata"
"github.com/cdcgov/data-exchange-upload/upload-server/internal/stores3"
metadataPkg "github.com/cdcgov/data-exchange-upload/upload-server/pkg/metadata"

"github.com/cdcgov/data-exchange-upload/upload-server/internal/appconfig"
"github.com/cdcgov/data-exchange-upload/upload-server/internal/health"
"github.com/cdcgov/data-exchange-upload/upload-server/internal/storeaz"
Expand Down
7 changes: 4 additions & 3 deletions upload-server/internal/delivery/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ package delivery
import (
"context"
"fmt"
"io"
"log/slog"
"strings"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/feature/s3/manager"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/cdcgov/data-exchange-upload/upload-server/internal/appconfig"
"github.com/cdcgov/data-exchange-upload/upload-server/internal/models"
"github.com/cdcgov/data-exchange-upload/upload-server/internal/stores3"
"io"
"log/slog"
"strings"
)

func NewS3Destination(ctx context.Context, target string, conn *appconfig.S3StorageConfig) (*S3Destination, error) {
Expand Down
Loading
Loading