I have a webtool (geoprocessing service) that generates a file at the end of its workflow.
How do I get it to automatically open the "download" window so that I can retrieve the file?
Thanks!
Well, this is both annoying and embarrassing.
As it turns out, the error lay not in anything crazy with permissions, but in the custom report name. One of the fields used in the report name (${casetype}) has values that contain spaces. So the report named "WYWY123456789__Telephone Lines.pdf" becomes "WYWY123456789__Telephone%20Lines.pdf" when output as a URL. However, there isn't any file named "WYWY123456789__Telephone%20Lines.pdf", so the url doesn't work.
Changing the report title scheme fixed the problem.
The other thing I found is that the step of copying the report seems to be load-bearing. I removed it after I got things working originally and it appears that the output was invalid unless you also copy.
In answer to my original question, setting the output to a DOCX will prompt that download window. I assume it's also the case for a zip file. PDFs like to open in the browser.
The final question that this workflow leaves is whether I need to somehow clear out the output folder somehow, but I feel like that's a problem for another day.
Thanks for the help everyone.
Edit: Here is my solution to get rid of those pesky bad characters by reversing the URL and whitelisting valid characters
import arcpy import arcgis import os import shutil import urllib class reportGenerator: def __init__(self): """Define the tool (tool name is the name of the class).""" self.label = "Report Generator" self.description = "" def getParameterInfo(self): """Define the tool parameters.""" inFeature = arcpy.Parameter(displayName='Input Feature', name='inFeature', datatype='GPFeatureRecordSetLayer', parameterType='Required', #multiValue = True, direction='Input') outFile = arcpy.Parameter(displayName='Output file', name='outFile', datatype='DEFile', parameterType='Optional', #multiValue = True, direction='Output') outFile.enabled = False outType = arcpy.Parameter(displayName='Output file format', name='outType', datatype='GPString', parameterType='Required', #multiValue = True, direction='Input') outType.filter.list = ["docx", "pdf"] outType.value = "pdf" # params = [ inFeature, outFile, outType ] return params def isLicensed(self): return True def updateParameters(self, params): return def updateMessages(self, params): return def execute(self, params, messages): """The source code of the tool.""" def main(oids: list, outType: str)-> None: ''' Takes the input features and exports them to a feature report in either PDF or Word format. :param oids: List of objectids to export :param outType: Output file format. Either PDF or DOCX. :return: None ''' scratchenv = arcpy.env.scratchFolder gis = arcgis.gis.GIS("home") sman = arcgis.apps.survey123.SurveyManager(gis) sID = sman.get('YourFormIDHere')#Form Item itemID. templates = sID.report_templates # Set the request message oids = [str(o) for o in oids] oids = ", ".join(oids) where_clause = f"objectid in ({oids})" # No case type because they contain spaces and that breaks # the URL. reportTitle = "${cse_nr}-${leg_cse_nr}${solotpass}${preappname}"#${inspecdate}" # Generate the report(s) pdf_report = sID.generate_report(templates[0], where= where_clause, output_format=outType,#) report_title = reportTitle) ''' Copy to the scratch folder This is load-bearing and must be done. ''' # Get rid of all bad characters by reverse-engineering the # url and then passing the name through the whitelist validchars = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', "_", "-", "."] out_name = os.path.basename(str(pdf_report)) out_name = urllib.parse.unquote(out_name) out_pdf = "" for char in out_name: if char in validchars: out_pdf += char out_pdf = out_pdf.strip("-_") out_pdf = os.path.join(scratchenv, out_pdf) shutil.copy(pdf_report, out_pdf) arcpy.AddMessage(f"Successfully exported. " f"Check the parameters to download.") # Set the output parameter to the address so that they can # find it easily. arcpy.SetParameter(1, out_pdf) return if __name__ == "__main__": # return # Set everything to a Featureset to work in Portal. inFeature = getPName(params, "inFeature").value inFeature = arcpy.FeatureSet(inFeature) geoJSON = json.loads(inFeature.JSON) totCount = arcpy.management.GetCount(r"https://pathtofeatureservice") totCount = int(totCount[0]) oids = [f["attributes"]["objectid"] for f in geoJSON["features"]] # Webtools aren't aware of map selections so this checks to # see if you actually made one. if len(oids) == totCount: arcpy.AddWarning("No selection was made. Tool not run.") return outType = getPName(params, "outType").value main(oids, outType) return def postExecute(self, params): return
I haven't thought of a good way to format any date fields in a way I like (They print to March 6, 2026 and I'd like 20260306) but the good news is this appears to export all my records just fine.
I've had success with making the file an output parameter, this outputs a URL in the old Web AppBuilder geoprocessing widget (I hope the ExB widget does the same thing, can't test that yet) and if you're doing manual HTTP work you should get the URL somewhere in the response data (sync) or you follow the URL trail from the final status update (async). What's the client you're using to run the tool?
Sooooo, I'm running this through Experience Builder on Enterprise.
I do get a url in the output parameter, but it goes to someplace that doesn't exist when I open it.
My tool is an attempt to get around the various issues I've been having with the feature report widget. By leaving the "folder_id" parameter blank for generate function, it allegedly should go to the scratch space by default? The output URLs seem to point to a scratch space, although I get a 400 error.
https://.../hosted/rest/directories/arcgisjobs/test_report_generator_gpserver/.../scratch/myreport.pdf
I'm trying to ask a colleague how he got his to work, but he's out of the office and also his tool works primarily via AGOL, so even though it may truly live on Enterprise, that might change things.
I know his tool uses the scratch environment (arcpy.env.scratchFolder).
I guess a potential workaround to try might be to like, manually copy the output to "scratch environment" and return that? Idk.
In any case, I think it'd be useful to force the download window open so that my users actually retrieve the report.
I double-checked a few of my tools and yes, you want to get your file into arcpy.env.scratchFolder and then set a derived File output parameter to the full path to that file. I forget exactly how to configure the parameter during publishing but I don't remember any gotchas. You can even leverage the zipfile module to write your output(s) into a nice little file for the user.
We have a geoprocessing service that outputs a pdf and/or a zip file, depending on the users needs. We send Survey123 records to the gp service from a Web AppBuilder application in ArcGIS Online. We return url's for the outputs to the web application, and the user clicks on the url's to pop open a new browser tab where the pdf/zip can be viewed/downloaded. I assume we could host the gp service on our Enterprise and have it configured identically. I don't think the web application "cares" where the gp service is running.
The gp service uses the ScratchFolder for outputs. When run locally during development the output is in some temp scratch folder in the user profile. When published to the server as a gp service the output adjusts to become the job scratch folder.
Here is an example output url, minus a few details specific to our implementation, showing the service job id and scratch in the path.
https://xyzxy/rest/directories/arcgisjobs/geoprocessingservices/XXXreportprocessphase2_gpserver/jc3fe28c323364f798cdefde93724b90a/scratch/xyzx-xy-xyz_xyz_Report_monitoring.pdf
Screenshot of the application with a url returned from the process for customer use
Our tool in Pro is configured like this (screenshot)
Hope some of that might be useful.
I feel like I have to be missing something pretty basic here.
Here is the tool from the pyt. (If I'm missing an import just assume that it's actually there).
import arcgis import arcpy import os import shutil class lafiReportGenerator: def __init__(self): """Define the tool (tool name is the name of the class).""" self.label = "Report Generator" self.description = "" def getParameterInfo(self): """Define the tool parameters.""" inFeature = arcpy.Parameter(displayName='Input Feature', name='inFeature', datatype='GPFeatureRecordSetLayer', parameterType='Required', #multiValue = True, direction='Input') outFile = arcpy.Parameter(displayName='Output file', name='outFile', datatype='DEFile', parameterType='Optional', #multiValue = True, direction='Output') outFile.enabled = False outType = arcpy.Parameter(displayName='Output file format', name='outType', datatype='GPString', parameterType='Required', direction='Input') outType.filter.list = ["docx", "pdf"] outType.value = "pdf" params = [ inFeature, outFile, outType ] # params = None return params def isLicensed(self): """Set whether the tool is licensed to execute.""" return True def updateParameters(self, params): """Modify the values and properties of parameters before internal validation is performed. This method is called whenever a parameter has been changed.""" return def updateMessages(self, params): """Modify the messages created by internal validation for each tool parameter. This method is called after internal validation.""" return def execute(self, params, messages): """The source code of the tool.""" def main(oids, outType): scratchenv = arcpy.env.scratchFolder gis = arcgis.gis.GIS("home") sman = arcgis.apps.survey123.SurveyManager(gis) sID = sman.get('ItemIDHere')#Form Item itemID. templates = sID.report_templates oids = [str(o) for o in oids] oids = ", ".join(oids) where_clause = f"objectid in ({oids})" reportTitle = "${cse_nr}_${leg_cse_nr}_${solotpass}_${casetype}" pdf_report = sID.generate_report(templates[0], where= where_clause, output_format=outType,#) report_title = reportTitle) out_pdf = os.path.join(scratchenv, os.path.basename(str(pdf_report))) shutil.copy(pdf_report, out_pdf) arcpy.AddMessage(out_pdf) arcpy.AddMessage(f"Successfully exported. " f"Check the parameters to download.") arcpy.SetParameter(1, out_pdf) return if __name__ == "__main__": # return inFeature = getPName(params, "inFeature").value inFeature = arcpy.FeatureSet(inFeature) geoJSON = json.loads(inFeature.JSON) totCount = arcpy.management.GetCount(r"https://myservicethat thesurveybelongsto/FeatureServer/0") totCount = int(totCount[0]) oids = [f["attributes"]["objectid"] for f in geoJSON["features"]] if len(oids) == totCount: arcpy.AddWarning("No selection was made. Tool not run.") return outType = getPName(params, "outType").value main(oids, outType) return def postExecute(self, params): """This method takes place after outputs are processed and added to the display.""" return
It works great in Pro. In EB I run the tool, it runs successfully, and I get a link of some sort in the output parameter. Clicking on the link sends me to a 400 error as I showed in my earlier post.
Good news is I copied the GP service to AGOL and it failed there too.
So, I'm reasonably sure this is user error on my end but I can't figure out what.
Los miembros registrados pueden publicar, seguir actualizaciones y más. ¿Nuevo aquí? Regístrate gratis.
Find useful guides, FAQs, and documents to help you navigate and make the most of Esri Community.