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: