Skip to content

Archive Trac docs question response

madscatt edited this page Jun 20, 2026 · 1 revision

Docs Question Response

Legacy Trac archive page imported from docs_question_response. Source: https://genapp.rocks/wiki/wiki/docs_question_response. Review age, links, and examples before treating as current.

question/response mechanism

  • Preliminary Design document

background

  • provides the ability for an executable to retrieve response from the user during the executable's run
  • not recommended for large multi-core jobs or when running via a batch queue system, since this will likely cause idling esp. if the user's response is significantly delayed

communications flow

  1. executable receives additional keys in input json object (similar to udp messaging)
  • e.g. input json example important keys
...
  "_tcpport"   : 1234,
  "_tcphost"   : "some.host.ip.address",
  "_uuid"      : "1234-5678-ABCD-EFGH",
...
  1. this tcp server supports both regular messaging (for reliable sends and for larger messages, since UDP is limited to 64kb) and questions
  2. all communications over this server use the standard json output format.
  3. questions are identified with the special "_question" key
  4. at the top level of the question JSON, a timeout can be provided.
...
  "timeout" : 1000,
...
  • this will pose a limit on the time available for a user to respond.
  • there will always be a system default timeout if you do not specify, which can vary depending on system setup
  1. the value of the "_question" key will be the question object in the following format
  "_question" : {
     "id" : "some_question_id_arbitrarily_assigned_by_the_executable"
     ,"title" : "a title for the question box"
# optionally add icon, default is question.png, standard options are information.png, warning.png, toast.png, skull.png, but other icons can be added (place in output/html5/pngs/) 
     ,"icon"  : "warning.png"
     ,"text" : "opening text, html ok"
# grid controls, see grid notes section
# specify number of columns for the grid (default 2)
     ,"grid" : 3
# specify more detail instead of just number of columns, this format replaces above
     ,"grid" : 
              { 
# optionally specify a gap between grid boxes, default "10px"
               "gap" : "5px"
# specify the number of columns and their relative widths
               ,"colwidths" : [ 1, 4 ]
# optionally specify alignment ("left" or "right" or "center"; default "left" )
               ,"align"   : "right"
              }
# buttons are optional, if not specified, an "OK" & "Cancel" will be shown
# any number of buttons can be specified
# the return button id will be the specified button text in lower case with all non alphanumeric characters removed,
# in the case below, that would correspond to values for the returned _response:button: of "button1" & "button2"
# note: any number of buttons can be specified
     ,"buttons" : [ "Button 1", "Button 2" ]
# optional alternate format for buttons with help and specified labels separate from id
     ,"buttons" : [ 
                   { 
                      "id" : "button1"
                      ,"label" : "My Button 1"
                      ,"help" : "help for this button" 
                   }
                   ,{ 
# id is optional, if not specified, it will be the label converted to lower case and all non alphanumeric characters removed
                      ,"label" : "My Button 2"
                      ,"help" : "help for button 2" 
# optionally add "skiprequired", this is useful for buttons like "cancel" or "done", it is set by default for "cancel" if buttons are not specified
                      ,"skiprequired" : "true"
                   }, 
# you can mix and match alternate button specs
                   "Button 3"
                   ]
# optionally add a required message, default is "Not all required fields have been entered."
     ,"requiredmsg" : "message to display if not all required fields are filled out"
     ,"fields" : [
# this is identical to the module field input, except that "role" is not required
# only a limited set of fields "type"s are currently supported
# initially supported: file, lrfile, text, checkbox, button
         {
            "type" : "label"
            ,"id"    : "unique_id_for_this_entry"
            ,"label" : "text can be html formatted"
            ,"help"  : "some hover help text - optional"
         }
         ,{
            "type" : "text"
            ,"id"    : "unique_id_1_for_this_entry"
            ,"help"  : "some hover help text - optional"
# optionally add "required", note if you are using custom buttons, you might consider setting one to "skiprequired":"true"
            ,"required" : "true"
         }
         ,{
            "type" : "integer"
            ,"id"    : "unique_id_1_for_this_entry"
# optionally set a default value
            ,"default" : 23
# optionally set a minimum value
            ,"min"   : 1
# optionally set a maximum value
            ,"min"   : 100
            ,"help"  : "some hover help text - optional"
# optionally add "required", note if you are using custom buttons, you might consider setting one to "skiprequired":"true"
            ,"required" : "true"
         }
         ,{
            "type" : "float"
            ,"id"    : "unique_id_1_for_this_entry"
# optionally set a default value
            ,"default" : 123.456
# optionally set a minimum value
            ,"min"   : 1.0
# optionally set a maximum value
            ,"min"   : 100.0
            ,"help"  : "some hover help text - optional"
# optionally add "required", note if you are using custom buttons, you might consider setting one to "skiprequired":"true"
            ,"required" : "true"
         }
# note that textarea is currently for output only (i.e. readonly)
         ,{
            "type" : "textarea"
            ,"id"    : "unique_id_1_for_this_entry"
            ,"label" : "an optional label"
            ,"rows"  : 10
            ,"cols"  : 80
            ,"default" : "The text that will appear\ncan be multiline"
            ,"help"  : "some hover help text - optional"
         }
# file 
# the file will be uploaded into a subdirectory "uploads" in the original directory of the running job.
# if a file of the same name already exists in this directory, a -1 or -2 etc. will be added to the name of the file
# before writing.  
# the value of the "id" key in the questions response will have the final file name.
         ,{
           "type" : "file"
           ,"id"    : "unique_id_2_for_this_entry"
           ,"label" : "upload a file"
           ,"help"  : "some hover help text - optional"
# optionally add "required", note if you are using custom buttons, you might consider setting one to "skiprequired":"true"
            ,"required" : "true"
        }
# lrfile 
# the file will be uploaded into a subdirectory "uploads" in the original directory of the running job.
# if a file of the same name already exists in this directory, a -1 or -2 etc. will be added to the name of the file
# before writing.  
# note - if the file uploaded from the server is already in the uploads directory (i.e. identical), it will not be renamed.
# the value of the "id" key in the questions response will have the final file name.
         ,{
           "type" : "lrfile"
           ,"id"    : "unique_id_3_for_this_entry"
           ,"label" : "upload a file"
           ,"help"  : "some hover help text - optional"
# optionally add "required", note if you are using custom buttons, you might consider setting one to "skiprequired":"true"
           ,"required" : "true"
        }
         ,{
            "type" : "checkbox"
            ,"id"    : "unique_id_4_for_this_entry"
            ,"label" : "checkbox text"
            ,"help"  : "some hover help text - optional"
         }
         ,{
            "type" : "listbox"
            ,"id"    : "unique_id_5_for_this_entry"
            ,"label" : "listbox label"
# for fixed alignment, add a monospace fontfamily
            ,"fontfamily" : "monospace"
            ,"values" : [
                          "row 1 value"
                          ,"row 2 value"
                        ]
# optionally specify return values, otherwise, you will get an index (0 based) into the values
            ,"returns" : [
                          "return value for row1"
                          ,"return value for row2"
                        ]
# optionally - if no "returns": are explicitly specified - select a default row by row number (0 based)
            ,"default" : 3
# optionally - if "returns": are explicitly specified - select a default row by specified return value
            ,"default" : "return value for row2"
# optionally display multiple rows
            ,"size"    : 5
            ,"help"  : "some hover help text - optional"
# optionally provide a "header" for the listbox
            ,"header"   : "header text for listbox"
# optionally provide a fontsize for the header
            ,"fontsize" : "0.93em"
# optionally select multiple
            ,"multiple" : "true"
# optionally add "required", note if you are using custom buttons, you might consider setting one to "skiprequired":"true"
            ,"required" : "true"
         }
     ]
   }
  1. upon sending of this message to the tcp server, the client will wait for a response on the tcp connection
  • an exit timeout should be imposed in the executable, since the user may never respond
  1. this message will be forwarded to the client UI by the tcpserver and the question will be logged in the mongodb (possibly multiple clients) via the standard zmq->websocket mechanism
  2. the client UI will recognize the "_question", display the question to the user in a modal box
  3. when the first client user responds, a message will ajax post to a php script which will in turn, allow the tcp server to send a response
  • simultaneous client attempts (multiple clients attached) on the same message will receive notification that this message has already been answered
  • if simple enough, they will also receive the value of the other client's response
  1. the client will receive an response from the tcp connection in the following format (similar to a standard module input object:
  "_uuid"    : "1234-5678-ABCD-EFGH",
  "_response" : {
       "id" : "id of question"
       ,"unique id 1 for this entry" : "value"
       ,
   }

additional notes

  • a library will be created for various executable languages to simplify question/response for the executable as needed
    • e.g. python

grid notes

  • when "grid" has been defined at the top-level of the question as shown above, each field has optional grid controls.
  • it is only necessary to specify "grid" info a field if you would like to override defaults.
  • by default:
    • the grid starts at row 1 column 1
    • there are two columns
      • the 1st one is typically used for "label"s
      • the 2nd one is used for "data", such as the text box for inputing text, etc
    • when a "field" is processed
      • the current row will be incremented from the previous last row
      • the current column will be set to 1
    • if the label is present (i.e. not an empty string "", it can be a single space " " for a blank label)
    • it will be placed in the current column and the current column will be incremented
    • the data will be placed in the current column and the current column will be incremented (the final increment serves no real purpose at this time)
  • any field can be overridden, both the "label" and the "data".
    • if you wish to manually place a field, you must add the information to the field with a "grid" tag
    • the "grid" tag's value is an object with optional values for "label", "data" and "align".
    • the optional "label" and "data" tags' values are a list of numbers specifying the starting column, number of columns, starting row and number of rows
      • you do not have to provide all four values, i.e. just starting column or starting column and number of columns, etc, can be provided
      • if you are going to give a starting row, you will currently have to keep track of the row numbers yourself
    • the optional "align" tag's value is a string of either "left", "right" or "center"
      • the default value is "left" unless specified in the question's top-level "grid" definition
    • example 1 - putting the checkbox data in column 1 and the label in column 2:
        ,{
            "type" : "checkbox"
            ,"id"    : "unique_id_4_for_this_entry"
            ,"label" : "checkbox text"
            ,"help"  : "some hover help text - optional"
            ,"grid"  : {
                         "label" : [ 2 ]
                         ,"data" : [ 1 ]
                       }
         }
  • example 2 - putting a check box in columns 3 and 4 of row 1 and right aligning the text:
    • note that the next row will be 2, so if you are going to have further fields, you will likely
    • need to specify "grid" also for at least the subsequent field.
        ,{
            "type" : "checkbox"
            ,"id"    : "unique_id_4_for_this_entry"
            ,"label" : "checkbox text"
            ,"help"  : "some hover help text - optional"
            ,"grid"  : {
                         "label" : [ 3, 1, 1 ]
                         ,"data" : [ 4, 1, 1 ]
                         ,"align" : "right"
                       }
         }

python example

  • here is a python example of using the tcp server for question/response using the canonical "energy" module
#!/usr/bin/python
import json, sys, StringIO, socket

def tcpquestion(json_variables, question, timeout=300, buffersize=65536):
        # json_variables is the input json object
        # question is either a dict or json string
        # timeout is in seconds
        # buffersize is for the answer, so if you expect larger than 64k of total json string size, use a larger number

        if '_tcphost' not in json_variables or '_tcpport' not in json_variables:
                return json.loads('{"error":"no tcp support"}')

        # build question

        msg = {}
        msg['_uuid'] = json_variables['_uuid']
        msg['timeout'] = timeout
        if isinstance( question, basestring ):
                try:
                        msg['_question'] = json.loads(question)
                except ValueError:
                        return json.loads('{"error":"json question decoding error"}')
        elif isinstance( question, dict ):
                msg['_question'] = question
        else:
                return json.loads('{"error":"question must be a json string or dict"}')

        msgj = json.dumps(msg)
        # a newline is also required when sending a question
        msgj += '\n'

        # send question
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((json_variables['_tcphost'],json_variables['_tcpport']))
        s.send(msgj)

        # receive answer

        data = s.recv(buffersize)
        s.close()
        return json.loads(data)

if __name__=='__main__':

        argv_io_string = StringIO.StringIO(sys.argv[1])
        json_variables = json.load(argv_io_string)

        mass = float(json_variables['m'])
        speed_of_light = float(json_variables['c'])

        import mass_energy

        output = {}

        # ask silly question if possible

        myquestion = '''
{
    "id" : "q1"
    ,"title" : "are you sure?"
    ,"text" : "<p>header text.</p><hr>"
    ,"fields" : [
        {
            "id" : "l1"
            ,"type" : "label"
            ,"label" : "<center>this is label text</center>"
        }
        ,{
            "id" : "t1"
            ,"type" : "text"
            ,"label" : "tell me your name:"
        }
        ,{
            "id" : "cb1"
            ,"type" : "checkbox"
            ,"label" : "are you sure about the speed of light?"
        }
    ]
}
'''.strip()
        answer = tcpquestion( json_variables, myquestion );

# alternatively, one could pass a dict
#        mydict = json.loads(myquestion)
#        ... manipulations of mydict ...
#        answer = tcpquestion( json_variables, mydict );

        if 'error' in answer:
                # handle error, could be timeout, no tcp support, malformed question json or other
                output['question_error'] = answer['error']

        if answer['_response']['button'] == 'cancel':
                # the user pressed cancel, handle it
                output['user_input'] = 'user has canceled'

        output['users_name'] = answer['_response']['t1']

        output['e'] = mass_energy.einstein(mass,speed_of_light)
        output['answer'] = json.dumps(answer)

        print json.dumps(output)

Clone this wiki locally