Unable to upload file using EuiFilePicker

Hi Team,

I have created a custom plugin and i have a requirement to upload file to server. I am trying to do it using EuiFilePicker. API which I am using to upload file is accepting data in form-data format. Below is my code.

component.ts :-

    const [files, setFiles] = useState({});
    const [large, setLarge] = useState(true);

    const onFileChange = files => {
        setFiles(files);
    };

    const onSubmit = () => {
        const data = new FormData();
        data.append('file', files[0]);
        data.append('remark', 'Test');
        data.append('case_id', 'INC-000001');
        
        const submitRequestOptions = {
            method: 'POST',
            body: data
        }

        props.http.post(window.location.origin + '/casemanagement/files/', submitRequestOptions).then((res) => {
            console.log("Success Response", res);

        }).catch((error: ResponseError) => {
            console.log("Error", error);
        });
    }

    const renderFiles = () => {
        if (files.length > 0) {
            return (
                <ul>
                    {Object.keys(files).map((item, i) => (
                        <li key={i}>
                            <strong>{files[item].name}</strong> ({files[item].size} bytes)
                        </li>
                    ))}
                </ul>
            );
        } else {
            return (
                <p>Add some files to see a demo of retrieving from the FileList</p>
            );
        }
    };

    return (
        <EuiPanel paddingSize="m" className="mr10 ml10">
            <EuiFlexGroup>
                <EuiFlexItem>
                    <EuiFilePicker
                        id="asdf2"
                        multiple
                        initialPromptText="Select or drag and drop multiple files"
                        onChange={files => {
                            onFileChange(files);
                        }}
                        display={large ? 'large' : 'default'}
                        aria-label="Use aria labels when no actual label is in use"
                    />
                </EuiFlexItem>
                <EuiFlexItem>
                    <EuiText>
                        <h3>Files attached</h3>
                        {renderFiles()}
                    </EuiText>
                </EuiFlexItem>
            </EuiFlexGroup>
            <EuiButton fill id='btnStep4Submit' color="primary" onClick={() => { onSubmit()}}>
                <strong>Submit</strong>
            </EuiButton>
        </EuiPanel>
    )

Server-route-index.ts api calling code.

router.post(
    {
      path: '/casemanagement/files/',
      validate: {
        body: schema.any(),
      },
    },
    async (context, request, response) => {
      const requestOptions = {
        method: 'POST',
        headers: { 'Content-Type': 'multipart/form-data', 'Cluster': process.env["cluster"], 'Authorization': request.headers.authorization },
        body: request.body,
        agent: httpsAgent
      };
      console.log("Request - ", request.body)
      return fetch(serverURL + '/casemanagement/cases/files/', requestOptions)
        .then(resp => {
          logger.info('Correlation-App : casemanagement close success');
          console.log("Response", resp);
          if (resp.status == 201) {
            return resp
          } else {
            throw new Error(resp)
          }
        }).then(res => {
          return response.ok({
            body: {
              Response: res
            },
          });
        })
        .catch(async (err) => {
          logger.info('Correlation-App : ' + err);
          let message_ = await err.text().then(mm => {
            return mm;
          });
          var resp_ = response.custom({
            body: {
              message: message_
            },
            statusCode: err.status
          });
          return resp_;
        });
    }
  );

I have tried uploading file through postman it works fine below is the screenshot of postman

If any example is there on how to upload file it would be better.

Thanks & Regards
Praveer

Hi @praveer
Can you confirm that the file data present (files[0]) when calling onSubmit is correct or incorrect?
If the data is correct, then EuiFIlePicker is working as expected and the problem likely lies with the onSubmit function.

Hi @thompsongl

Below is the screen of console log of files[0].

This is what I am passing to my api.

Thanks. It appears that EuiFilePicker is working as expected. The error is coming from the HTTP request, but I'm familiar enough with plugin APIs to diagnose the issue beyond that.
I'll ping someone with more knowledge on the subject.

I think the problem is coming from the submitRequestOptions. I believe the HTTP client is not automatically appending the Header to post the FormData. So the body-parser on the server doesn't know how to handle the contents of the body and fails with Bad request.

Would you mind manually adding the property headers: {'content-type': 'multipart/form-data'} to the submitRequestOptions and try if that fixes the issue?

Hi @afharo ,

I tried passing headers: {'content-type': 'multipart/form-data'} as shown below

const onSubmit = () => {
        const data = new FormData();
        data.append('file', files[0]);
        data.append('remark', 'Test');
        data.append('case_id', 'INC-000001');
        console.log("Files - ", files[0]);
        const submitRequestOptions = {
            method: 'POST',
            headers: { 'Content-Type': 'multipart/form-data' },
            body: data
        }

        props.http.post(window.location.origin + '/casemanagement/files/', submitRequestOptions).then((res) => {
            console.log("Success Response", res);

        }).catch((error: ResponseError) => {
            console.log("Error", error);
        });
    }

Server-route-index.ts api calling code.

router.post(
    {
      path: '/casemanagement/files/',
      validate: {
        body: schema.any(),
      },
    },
    async (context, request, response) => {
      const requestOptions = {
        method: 'POST',
        headers: { 'Content-Type': 'multipart/form-data', 'Cluster': process.env["cluster"], 'Authorization': request.headers.authorization },
        body: request.body,
        agent: httpsAgent
      };
      console.log("Request - ", request.body)
      return fetch(serverURL + '/casemanagement/cases/files/', requestOptions)
        .then(resp => {
          logger.info('Correlation-App : casemanagement close success');
          console.log("Response", resp);
          if (resp.status == 202) {
            return resp
          } else {
            throw new Error(resp)
          }
        }).then(res => {
          return response.ok({
            body: {
              Response: res
            },
          });
        })
        .catch(async (err) => {
          logger.info('Correlation-App : ' + err);
          let message_ = await err.text().then(mm => {
            return mm;
          });
          var resp_ = response.custom({
            body: {
              message: message_
            },
            statusCode: err.status
          });
          return resp_;
        });
    }
  );

Below are the attached screen shot of the payload passed in headers and its response
fileFormDataPayload

filleFormDataResponse

Can you please help.

From the response, I get that the multipart header is missing the boundary. You might need to add it. If you look at the headers Postman adds, when using Content-Type: 'multipart/form-data', the boundary= bit needs to be added (MDN Web docs).

Here are some examples of possible options: javascript - fetch - Missing boundary in multipart/form-data POST - Stack Overflow. Apparently, some users made it work by explicitly setting Content-Type: undefined, and letting fetch to populate it automatically (FYI, we use fetch under the hood). Others had to add 'Accept': '*/*' to the request as well :man_shrugging:

Hi @afharo,

I tried ur suggestion, but in the above suggestion file is going as binary format and getting bad request error.

In EUIFilePicker how can I get file path as I am trying to send payload as below for that I need to get the file path of uploaded file, but I couldn't find it in EUIFilePicker

var formdata = new FormData();
formdata.append("file", fileInput.files[0], "/C:/Users/Praveer.Nair/Desktop/cases.json");
formdata.append("remark", "Test");
formdata.append("case_id", "INC-000001");

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: formdata,
  redirect: 'follow'
};

I think @thompsongl can help with that better than I can :slight_smile:

EuiFilePicker is really just a styled native HTML <input type="file"> element, so all the data you get is from the Web API: File - Web APIs | MDN.
Looks like webkitRelativePath might be helpful, but EUI does not actually handle the file itself or provide any data beyond the Web API.

1 Like

hi @thompsongl ,

WebkitRelativePath is coming empty as shown in below screen shot.

I just reread the spec and webkitRelativePath will not be helpful for your case:

The File.webkitRelativePath is a read-only property that contains a USVString which specifies the file's path relative to the directory selected by the user in an <input> element with its webkitdirectory attribute set.

It only works when selecting directories and when using the webkitdirectory attribute.
Unfortunately the data you want is not available in the Web API.
Although EuiFilePicker cannot provide this data, formdata.append("file", fileInput.files[0]) appears to be sufficient per the FormData spec: FormData: append() method - Web APIs | MDN

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.