graphene-file-upload icon indicating copy to clipboard operation
graphene-file-upload copied to clipboard

No UI example found

Open amal-chandran opened this issue 5 years ago • 8 comments

We are trying to use this in our project not able to create a ui not even properly test.we need an documentation/ examples including ui

amal-chandran avatar Mar 01 '19 20:03 amal-chandran

What do you mean by UI example?

GasimGasimzada avatar Jul 07 '19 11:07 GasimGasimzada

There is no client side code in the example only backend code is there. A React.JS implementation is missing in the example.

amal-chandran avatar Jul 08 '19 05:07 amal-chandran

This is a backend implementation for GraphQL multipart specification. There separate frontend repositories for different frameworks and libraries. Here is the repo for the specification: https://github.com/jaydenseric/graphql-multipart-request-spec/blob/master/readme.md. If you check the end of README, you will see different repos for frameworks / libraries for both client and server.

I think it doesn’t make sense to show examples for frontend here because it is based on a specification written by a 3rd party. Plus, If React example is added, Angular and Vue examples should also be added. The specification’s repo has all the necessary information for different tools.

GasimGasimzada avatar Jul 08 '19 06:07 GasimGasimzada

for those who are still struggling with this (as I did) here is a code that may help Please be aware of the endpoint URL, which has to be adjusted to your needs.

import { print } from 'graphql/language/printer';

const createOptions = (opts) => {
  const formData = new FormData();

  opts.variables.files.forEach((file, i) => {
    formData.append('map', `{"${i}": ["variables.files.${i}"] }`);
    formData.append(`${i}`, file, file.name);
  });

  const files_null = opts.variables.files.map(() => null) // one null for each file, e.g. "null,null,null"
  const variables = {...opts.variables}
  variables.files = files_null

  //see https://stackoverflow.com/questions/57490569/import-graphql-query-gql-file-into-plain-javascript-and-run-an-axios-call
  const value = {
    query: print(opts.query),
    variables: variables
  }
  formData.append('operations', JSON.stringify(value).replace(/\\n/g, ''))

  return {
    method: 'POST',
    credentials: 'include',
    body: formData,
  }
}

const uploadFile = (opts) => {
  console.assert(opts.variables.files.length > 0, 'files is empty');
  return fetch(
    `${process.env.NEXT_PUBLIC_BACKEND}upload/`,
    createOptions(opts)
  );
};

// const [uploadFiles, { status }] = useUploadFiles()
export default uploadFile;

Mutation

export const UPLOAD_FILES = gql`
  mutation uploadFiles($files: [Upload]!) {
    uploadFiles(files: $files) {
      ok
      urls {
        url
        name
      }
    }
  }
`

Call: just use an input type="file" and call

uploadFIle({query: UPLOAD_FILES, variables: { files: [e.target.files[0]] }}

luiskarlos avatar Jun 07 '22 00:06 luiskarlos

@luiskarlos I am doing the same thing, but getting an empty dict on the server (Django/graphene) side. Also in that last line you posted, it should be 'mutation: UPLOAD_FILES' rather than 'query: UPLOAD_FILES', right? Here is my (failing) code. Perhaps you (or someone else) can spot something obvious:

const UPLOAD_NEW_FILE = gql `
mutation UploadNewFile($fileToUpload: Upload!) {
  uploadNewFile(file: $fileToUpload) {
    success
  }
}`;

  uploadFile(agFile: File): Observable<FetchResult<any>>  {
    return this.apollo.mutate({
      mutation: UPLOAD_NEW_FILE,
      variables: {
        fileToUpload: agFile
      }
    });
  

And I am calling this method like this:

  onFileUpload(event) {
    const myFile = event.target.files[0]
    this.configSvc.uploadFile(myFile).subscribe(({ data }) => {
      console.log("Upload of package was " + data.success)
    })
  }

onFileUpload is invoked from an 'input type="file" HTML element. I have printed (console.log) the myFile variable in onFileUpload handler function and the 'agFile' variable in the uploadFile service function right before the mutation call, and they indeed are file objects. But for whatever reason, when apollo picks it up it makes it into an empty dict.

Any help is much appreciated.

boomdizz avatar Dec 08 '22 17:12 boomdizz

Also adding that the above mutation is working from the Altair graphql client.

boomdizz avatar Dec 08 '22 17:12 boomdizz

@boomdizz This is the code I am using right now, my backend is with python so I am not sure how it will work with js:

this is a utility uploadFile.js

import { print } from 'graphql/language/printer';

const createOptions = (opts) => {
  const formData = new FormData();

  opts.variables.files.forEach((file, i) => {
    formData.append('map', `{"${i}": ["variables.files.${i}"] }`);
    formData.append(`${i}`, file, file.name);
  });

  const files_null = opts.variables.files.map(() => null); // one null for each file, e.g. "null,null,null"
  const variables = { ...opts.variables };
  variables.files = files_null;

  //see https://stackoverflow.com/questions/57490569/import-graphql-query-gql-file-into-plain-javascript-and-run-an-axios-call
  const value = {
    query: print(opts.query),
    variables: variables,
  };
  formData.append('operations', JSON.stringify(value).replace(/\\n/g, ''));

  return {
    method: 'POST',
    credentials: 'include',
    body: formData,
  };
};

const uploadFile = (opts) => {
  console.assert(opts.variables.files.length > 0, 'files is empty');
  return fetch(
    `${process.env.NEXT_PUBLIC_BACKEND}upload/`,
    createOptions(opts)
  );
};

export default uploadFile;

This a hook

import { useEffect, useState } from 'react';
import uploadFile from '../utils/uploadFile';

const useUpload = (query) => {
  const [options, upload] = useState({ variables: {} });
  const [status, setStatus] = useState({
    uploading: false,
    success: false,
    error: false,
    data: null,
    message: null,
  });

  const updateStatus = (uploading, success, error, data, message) =>
    setStatus({ uploading, success, error, data, message });

  useEffect(() => {
    if (options.variables?.files?.length) {
      updateStatus(true, false, false, null, 'Uploading...');

      uploadFile({ query, ...options })
        .then((result) => {
          return result.json();
        })
        .then((result) => {
          if (result.errors) {
            throw new Error(result.errors[0].message);
          }

          updateStatus(
            false,
            true,
            false,
            result.data,
            'Files Uploaded Successfully'
          );
        })
        .catch((error) => {
          updateStatus(false, false, true, error.message);
          console.log('error', error);
        });
    }
  }, [options]);

  return [upload, { ...status }];
};

// const [uploadFiles, { status }] = useUploadFiles()
export default useUpload;

This is the query:

export const UPLOAD_FILES = gql`
  mutation uploadFiles($files: [Upload]!) {
    uploadFiles(files: $files) {
      ok
    }
  }
`;

This is my mutation:

class UploadFilesMutation(graphene.Mutation):

    class Arguments:
        files = graphene.List(Upload)

    file = graphene.Field(FileType)

    def mutate(self, info, files,  **kwargs):
        rf = StorageSystem.save_file(info.context.user, files[0], public=False)
        return UploadFilesMutation(document=document)

luiskarlos avatar Dec 10 '22 15:12 luiskarlos

I was able to finally get it to work in the Angular/Apollo environment. I have it documented as the answer to my own question on Stackoverflow

boomdizz avatar Dec 14 '22 00:12 boomdizz