github.com/muhammedhassanm/blockchain@v0.0.0-20200120143007-697261defd4d/sawtooth-supply-chain-master/fish_client/src/views/fish_detail.js (about)

     1  /**
     2   * Copyright 2017 Intel Corporation
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   * ----------------------------------------------------------------------------
    16   */
    17  'use strict'
    18  
    19  const m = require('mithril')
    20  const moment = require('moment')
    21  const truncate = require('lodash/truncate')
    22  
    23  const {MultiSelect} = require('../components/forms')
    24  const payloads = require('../services/payloads')
    25  const parsing = require('../services/parsing')
    26  const transactions = require('../services/transactions')
    27  const api = require('../services/api')
    28  const {
    29    getPropertyValue,
    30    getLatestPropertyUpdateTime,
    31    getOldestPropertyUpdateTime,
    32    isReporter
    33  } = require('../utils/records')
    34  
    35  /**
    36   * Possible selection options
    37   */
    38  const authorizableProperties = [
    39    ['location', 'Location'],
    40    ['temperature', 'Temperature'],
    41    ['tilt', 'Tilt'],
    42    ['shock', 'Shock']
    43  ]
    44  
    45  const _labelProperty = (label, value) => [
    46    m('dl',
    47      m('dt', label),
    48      m('dd', value))
    49  ]
    50  
    51  const _row = (...cols) =>
    52    m('.row',
    53      cols
    54      .filter((col) => col !== null)
    55      .map((col) => m('.col', col)))
    56  
    57  const TransferDropdown = {
    58    view (vnode) {
    59      // Default to no-op
    60      let onsuccess = vnode.attrs.onsuccess || (() => null)
    61      let record = vnode.attrs.record
    62      let role = vnode.attrs.role
    63      let publicKey = vnode.attrs.publicKey
    64      return [
    65        m('.dropdown',
    66          m('button.btn.btn-primary.btn-block.dropdown-toggle.text-left',
    67            { 'data-toggle': 'dropdown' },
    68            vnode.children),
    69          m('.dropdown-menu',
    70            vnode.attrs.agents.map(agent => {
    71              let proposal = _getProposal(record, agent.key, role)
    72              return [
    73                m("a.dropdown-item[href='#']", {
    74                  onclick: (e) => {
    75                    e.preventDefault()
    76                    if (proposal && proposal.issuingAgent === publicKey) {
    77                      _answerProposal(record, agent.key, ROLE_TO_ENUM[role],
    78                                      payloads.answerProposal.enum.CANCEL)
    79                        .then(onsuccess)
    80                    } else {
    81                      _submitProposal(record, ROLE_TO_ENUM[role], agent.key)
    82                        .then(onsuccess)
    83                    }
    84                  }
    85                }, m('span.text-truncate',
    86                     truncate(agent.name, { length: 32 }),
    87                     (proposal ? ' \u2718' : '')))
    88              ]
    89            })))
    90      ]
    91    }
    92  }
    93  
    94  const ROLE_TO_ENUM = {
    95    'owner': payloads.createProposal.enum.OWNER,
    96    'custodian': payloads.createProposal.enum.CUSTODIAN,
    97    'reporter': payloads.createProposal.enum.REPORTER
    98  }
    99  
   100  const TransferControl = {
   101    view (vnode) {
   102      let {record, agents, publicKey, role, label} = vnode.attrs
   103      if (record.final) {
   104        return null
   105      }
   106  
   107      let onsuccess = vnode.attrs.onsuccess || (() => null)
   108      if (record[role] === publicKey) {
   109        return [
   110          m(TransferDropdown, {
   111            publicKey,
   112            agents,
   113            record,
   114            role,
   115            onsuccess
   116          }, `Transfer ${label}`)
   117        ]
   118      } else if (_hasProposal(record, publicKey, role)) {
   119        return [
   120          m('.d-flex.justify-content-start',
   121            m('button.btn.btn-primary', {
   122              onclick: (e) => {
   123                e.preventDefault()
   124                _answerProposal(record, publicKey, ROLE_TO_ENUM[role],
   125                                payloads.answerProposal.enum.ACCEPT)
   126  
   127                  .then(onsuccess)
   128              }
   129            },
   130            `Accept ${label}`),
   131            m('button.btn.btn-danger.ml-auto', {
   132              onclick: (e) => {
   133                e.preventDefault()
   134                _answerProposal(record, publicKey, ROLE_TO_ENUM[role],
   135                                payloads.answerProposal.enum.REJECT)
   136                  .then(onsuccess)
   137              }
   138            },
   139            `Reject`))
   140        ]
   141      } else {
   142        return null
   143      }
   144    }
   145  }
   146  
   147  const _getProposal = (record, receivingAgent, role) =>
   148    record.proposals.find(
   149      (proposal) => (proposal.role.toLowerCase() === role && proposal.receivingAgent === receivingAgent))
   150  
   151  const _hasProposal = (record, receivingAgent, role) =>
   152    !!_getProposal(record, receivingAgent, role)
   153  
   154  const ReporterControl = {
   155    view (vnode) {
   156      let {record, agents, publicKey} = vnode.attrs
   157      if (record.final) {
   158        return null
   159      }
   160  
   161      let onsuccess = vnode.attrs.onsuccess || (() => null)
   162      if (record.owner === publicKey) {
   163        return [
   164          m(AuthorizeReporter, {
   165            record,
   166            agents,
   167            onsubmit: ([publicKey, properties]) =>
   168            _authorizeReporter(record, publicKey, properties).then(onsuccess)
   169          }),
   170  
   171          // Outstanding reporters
   172          Object.entries(_reporters(record))
   173          .filter(([key, _]) => key !== publicKey)
   174          .map(([key, properties]) => {
   175            return [
   176              m('.mt-2.d-flex.justify-content-start',
   177                `${_agentByKey(agents, key).name} authorized for ${properties}`,
   178                m('.button.btn.btn-outline-danger.ml-auto', {
   179                  onclick: (e) => {
   180                    e.preventDefault()
   181                    _revokeAuthorization(record, key, properties)
   182                      .then(onsuccess)
   183                  }
   184                },
   185                'Revoke Authorization'))
   186            ]
   187          }),
   188  
   189          // Pending authorizations
   190          record.proposals.filter((p) => p.role === 'REPORTER' && p.issuingAgent === publicKey).map(
   191            (p) =>
   192              m('.mt-2.d-flex.justify-content-start',
   193                `Pending proposal for ${_agentByKey(agents, p.receivingAgent).name} on ${p.properties}`,
   194                m('.button.btn.btn-outline-danger.ml-auto',
   195                  {
   196                    onclick: (e) => {
   197                      e.preventDefault()
   198                      _answerProposal(record, p.receivingAgent, ROLE_TO_ENUM['reporter'],
   199                                      payloads.answerProposal.enum.CANCEL)
   200                        .then(onsuccess)
   201                    }
   202                  },
   203                  'Rescind Proposal')))
   204  
   205        ]
   206      } else if (_hasProposal(record, publicKey, 'reporter')) {
   207        let proposal = _getProposal(record, publicKey, 'reporter')
   208        return [
   209          m('.d-flex.justify-content-start',
   210            m('button.btn.btn-primary', {
   211              onclick: (e) => {
   212                e.preventDefault()
   213                _answerProposal(record, publicKey, ROLE_TO_ENUM['reporter'],
   214                                payloads.answerProposal.enum.ACCEPT)
   215                  .then(onsuccess)
   216              }
   217            },
   218            `Accept Reporting Authorization for ${proposal.properties}`),
   219            m('button.btn.btn-danger.ml-auto', {
   220              onclick: (e) => {
   221                e.preventDefault()
   222                _answerProposal(record, publicKey, ROLE_TO_ENUM['reporter'],
   223                                payloads.answerProposal.enum.REJECT)
   224                  .then(onsuccess)
   225              }
   226            },
   227            `Reject`))
   228        ]
   229      } else {
   230        return null
   231      }
   232    }
   233  }
   234  
   235  /**
   236   * Returns a map of reporter key, to authorized fields
   237   */
   238  const _reporters = (record) =>
   239    record.properties.reduce((acc, property) => {
   240      return property.reporters.reduce((acc, key) => {
   241        let props = (acc[key] || [])
   242        props.push(property.name)
   243        acc[key] = props
   244        return acc
   245      }, acc)
   246    }, {})
   247  
   248  const _agentByKey = (agents, key) =>
   249    agents.find((agent) => agent.key === key) || { name: 'Unknown Agent' }
   250  
   251  const _agentLink = (agent) =>
   252    m(`a[href=/agents/${agent.key}]`,
   253      { oncreate: m.route.link },
   254      agent.name)
   255  
   256  const _propLink = (record, propName, content) =>
   257    m(`a[href=/properties/${record.recordId}/${propName}]`,
   258      { oncreate: m.route.link },
   259      content)
   260  
   261  const ReportLocation = {
   262    view: (vnode) => {
   263      let onsuccess = vnode.attrs.onsuccess || (() => null)
   264      return [
   265        m('form', {
   266          onsubmit: (e) => {
   267            e.preventDefault()
   268            _updateProperty(vnode.attrs.record, {
   269              name: 'location',
   270              locationValue: {
   271                latitude: parsing.toInt(vnode.state.latitude),
   272                longitude: parsing.toInt(vnode.state.longitude)
   273              },
   274              dataType: payloads.updateProperties.enum.LOCATION
   275            }).then(() => {
   276              vnode.state.latitude = ''
   277              vnode.state.longitude = ''
   278            })
   279            .then(onsuccess)
   280          }
   281        },
   282        m('.form-row',
   283          m('.form-group.col-5',
   284            m('label.sr-only', { 'for': 'latitude' }, 'Latitude'),
   285            m("input.form-control[type='text']", {
   286              name: 'latitude',
   287              type: 'number',
   288              step: 'any',
   289              min: -90,
   290              max: 90,
   291              onchange: m.withAttr('value', (value) => {
   292                vnode.state.latitude = value
   293              }),
   294              value: vnode.state.latitude,
   295              placeholder: 'Latitude'
   296            })),
   297          m('.form-group.col-5',
   298            m('label.sr-only', { 'for': 'longitude' }, 'Longitude'),
   299            m("input.form-control[type='text']", {
   300              name: 'longitude',
   301              type: 'number',
   302              step: 'any',
   303              min: -180,
   304              max: 180,
   305              onchange: m.withAttr('value', (value) => {
   306                vnode.state.longitude = value
   307              }),
   308              value: vnode.state.longitude,
   309              placeholder: 'Longitude'
   310            })),
   311  
   312          m('.col-2',
   313            m('button.btn.btn-primary', 'Update'))))
   314      ]
   315    }
   316  }
   317  
   318  const ReportValue = {
   319    view: (vnode) => {
   320      let onsuccess = vnode.attrs.onsuccess || (() => null)
   321      let xform = vnode.attrs.xform || ((x) => x)
   322      return [
   323        m('form', {
   324          onsubmit: (e) => {
   325            e.preventDefault()
   326            _updateProperty(vnode.attrs.record, {
   327              name: vnode.attrs.name,
   328              [vnode.attrs.typeField]: xform(vnode.state.value),
   329              dataType: vnode.attrs.type
   330            }).then(() => {
   331              vnode.state.value = ''
   332            })
   333            .then(onsuccess)
   334          }
   335        },
   336          m('.form-row',
   337            m('.form-group.col-10',
   338              m('label.sr-only', { 'for': vnode.attrs.name }, vnode.attrs.label),
   339              m("input.form-control[type='text']", {
   340                name: vnode.attrs.name,
   341                onchange: m.withAttr('value', (value) => {
   342                  vnode.state.value = value
   343                }),
   344                value: vnode.state.value,
   345                placeholder: vnode.attrs.label
   346              })),
   347           m('.col-2',
   348             m('button.btn.btn-primary', 'Update'))))
   349      ]
   350    }
   351  }
   352  
   353  const ReportTilt = {
   354    view: (vnode) => {
   355      let onsuccess = vnode.attrs.onsuccess || (() => null)
   356      return [
   357        m('form', {
   358          onsubmit: (e) => {
   359            e.preventDefault()
   360            _updateProperty(vnode.attrs.record, {
   361              name: 'tilt',
   362              stringValue: JSON.stringify({
   363                x: parsing.toInt(vnode.state.x),
   364                y: parsing.toInt(vnode.state.y)
   365              }),
   366              dataType: payloads.updateProperties.enum.STRING
   367            })
   368            .then(() => {
   369              vnode.state.x = null
   370              vnode.state.y = null
   371            })
   372            .then(onsuccess)
   373          }
   374        },
   375        m('.form-row',
   376          m('.col.md-4.mr-1',
   377            m('input.form-control', {
   378              placeholder: 'Enter X...',
   379              type: 'number',
   380              step: 'any',
   381              oninput: m.withAttr('value', (value) => {
   382                vnode.state.x = value
   383              })
   384            })),
   385          m('.col.md-4',
   386            m('input.form-control', {
   387              placeholder: 'Enter Y...',
   388              type: 'number',
   389              step: 'any',
   390              oninput: m.withAttr('value', (value) => {
   391                vnode.state.y = value
   392              })
   393            })),
   394          m('.col-2',
   395            m('button.btn.btn-primary', 'Update'))))
   396      ]
   397    }
   398  }
   399  
   400  const ReportShock = {
   401    view: (vnode) => {
   402      let onsuccess = vnode.attrs.onsuccess || (() => null)
   403      return [
   404        m('form', {
   405          onsubmit: (e) => {
   406            e.preventDefault()
   407            _updateProperty(vnode.attrs.record, {
   408              name: 'shock',
   409              stringValue: JSON.stringify({
   410                accel: parsing.toInt(vnode.state.accel),
   411                duration: parsing.toInt(vnode.state.duration)
   412              }),
   413              dataType: payloads.updateProperties.enum.STRING
   414            })
   415            .then(() => {
   416              vnode.state.accel = null
   417              vnode.state.duration = null
   418            })
   419            .then(onsuccess)
   420          }
   421        },
   422        m('.form-row',
   423          m('.col.md-4.mr-1',
   424            m('input.form-control', {
   425              placeholder: 'Enter Acceleration...',
   426              type: 'number',
   427              step: 'any',
   428              min: 0,
   429              oninput: m.withAttr('value', (value) => {
   430                vnode.state.accel = value
   431              })
   432            })),
   433          m('.col.md-4',
   434            m('input.form-control', {
   435              placeholder: 'Enter Duration...',
   436              type: 'number',
   437              step: 'any',
   438              min: 0,
   439              oninput: m.withAttr('value', (value) => {
   440                vnode.state.duration = value
   441              })
   442            })),
   443          m('.col-2',
   444            m('button.btn.btn-primary', 'Update'))))
   445      ]
   446    }
   447  }
   448  
   449  const AuthorizeReporter = {
   450    oninit (vnode) {
   451      vnode.state.properties = []
   452    },
   453  
   454    view (vnode) {
   455      return [
   456        _row(m('strong', 'Authorize Reporter')),
   457        m('.row',
   458          m('.col-6',
   459            m('input.form-control', {
   460              type: 'text',
   461              placeholder: 'Add reporter by name or public key...',
   462              value: vnode.state.reporter,
   463              oninput: m.withAttr('value', (value) => {
   464                // clear any previously matched values
   465                vnode.state.reporterKey = null
   466                vnode.state.reporter = value
   467                let reporter = vnode.attrs.agents.find(
   468                  (agent) => agent.name === value || agent.key === value)
   469                if (reporter) {
   470                  vnode.state.reporterKey = reporter.key
   471                }
   472              })
   473            })),
   474  
   475          m('.col-4',
   476            m(MultiSelect, {
   477              label: 'Select Fields',
   478              color: 'primary',
   479              options: authorizableProperties,
   480              selected: vnode.state.properties,
   481              onchange: (selection) => {
   482                vnode.state.properties = selection
   483              }
   484            })),
   485  
   486          m('.col-2',
   487            m('button.btn.btn-primary',
   488              {
   489                disabled: (!vnode.state.reporterKey || vnode.state.properties.length === 0),
   490                onclick: (e) => {
   491                  e.preventDefault()
   492                  vnode.attrs.onsubmit([vnode.state.reporterKey, vnode.state.properties])
   493                  vnode.state.reporterKey = null
   494                  vnode.state.reporter = null
   495                  vnode.state.properties = []
   496                }
   497              },
   498              'Authorize')))
   499      ]
   500    }
   501  }
   502  
   503  const FishDetail = {
   504    oninit (vnode) {
   505      _loadData(vnode.attrs.recordId, vnode.state)
   506      vnode.state.refreshId = setInterval(() => {
   507        _loadData(vnode.attrs.recordId, vnode.state)
   508      }, 2000)
   509    },
   510  
   511    onbeforeremove (vnode) {
   512      clearInterval(vnode.state.refreshId)
   513    },
   514  
   515    view (vnode) {
   516      if (!vnode.state.record) {
   517        return m('.alert-warning', `Loading ${vnode.attrs.recordId}`)
   518      }
   519  
   520      let publicKey = api.getPublicKey()
   521      let owner = vnode.state.owner
   522      let custodian = vnode.state.custodian
   523      let record = vnode.state.record
   524      return [
   525        m('.fish-detail',
   526          m('h1.text-center', record.recordId),
   527          _row(
   528            _labelProperty('Created',
   529                           _formatTimestamp(getOldestPropertyUpdateTime(record))),
   530            _labelProperty('Updated',
   531                           _formatTimestamp(getLatestPropertyUpdateTime(record)))),
   532  
   533          _row(
   534            _labelProperty('Owner', _agentLink(owner)),
   535            m(TransferControl, {
   536              publicKey,
   537              record,
   538              agents: vnode.state.agents,
   539              role: 'owner',
   540              label: 'Ownership',
   541              onsuccess: () => _loadData(vnode.attrs.recordId, vnode.state)
   542            })),
   543  
   544          _row(
   545            _labelProperty('Custodian', _agentLink(custodian)),
   546            m(TransferControl, {
   547              publicKey,
   548              record,
   549              agents: vnode.state.agents,
   550              role: 'custodian',
   551              label: 'Custodianship',
   552              onsuccess: () => _loadData(vnode.attrs.recordId, vnode.state)
   553            })),
   554  
   555          _row(_labelProperty('Species', getPropertyValue(record, 'species'))),
   556  
   557          _row(
   558            _labelProperty('Length (m)', parsing.toFloat(getPropertyValue(record, 'length', 0))),
   559            _labelProperty('Weight (kg)', parsing.toFloat(getPropertyValue(record, 'weight', 0)))),
   560  
   561          _row(
   562            _labelProperty(
   563              'Location',
   564              _propLink(record, 'location', _formatLocation(getPropertyValue(record, 'location')))
   565            ),
   566            (isReporter(record, 'location', publicKey) && !record.final
   567             ? m(ReportLocation, { record, onsuccess: () => _loadData(record.recordId, vnode.state) })
   568             : null)),
   569  
   570          _row(
   571            _labelProperty(
   572              'Temperature',
   573              _propLink(record, 'temperature', _formatTemp(getPropertyValue(record, 'temperature')))),
   574            (isReporter(record, 'temperature', publicKey) && !record.final
   575            ? m(ReportValue,
   576              {
   577                name: 'temperature',
   578                label: 'Temperature (C°)',
   579                record,
   580                typeField: 'numberValue',
   581                type: payloads.updateProperties.enum.NUMBER,
   582                xform: (x) => parsing.toInt(x),
   583                onsuccess: () => _loadData(vnode.attrs.recordId, vnode.state)
   584              })
   585             : null)),
   586  
   587          _row(
   588            _labelProperty(
   589              'Tilt',
   590              _propLink(record, 'tilt', _formatValue(record, 'tilt'))),
   591            (isReporter(record, 'tilt', publicKey) && !record.final
   592             ? m(ReportTilt, {
   593               record,
   594               onsuccess: () => _loadData(vnode.attrs.recordId, vnode.state)
   595             })
   596             : null)),
   597  
   598          _row(
   599            _labelProperty(
   600              'Shock',
   601              _propLink(record, 'shock', _formatValue(record, 'shock'))),
   602            (isReporter(record, 'shock', publicKey) && !record.final
   603             ? m(ReportShock, {
   604               record,
   605               onsuccess: () => _loadData(vnode.attrs.recordId, vnode.state)
   606             })
   607             : null)),
   608  
   609          _row(m(ReporterControl, {
   610            record,
   611            publicKey,
   612            agents: vnode.state.agents,
   613            onsuccess: () => _loadData(vnode.attrs.recordId, vnode.state)
   614          })),
   615  
   616          ((record.owner === publicKey && !record.final)
   617           ? m('.row.m-2',
   618               m('.col.text-center',
   619                 m('button.btn.btn-danger', {
   620                   onclick: (e) => {
   621                     e.preventDefault()
   622                     _finalizeRecord(record).then(() =>
   623                       _loadData(vnode.attrs.recordId, vnode.state))
   624                   }
   625                 },
   626                 'Finalize')))
   627           : '')
   628         )
   629      ]
   630    }
   631  }
   632  
   633  const _formatValue = (record, propName) => {
   634    let prop = getPropertyValue(record, propName)
   635    if (prop) {
   636      return parsing.stringifyValue(parsing.floatifyValue(prop), '***', propName)
   637    } else {
   638      return 'N/A'
   639    }
   640  }
   641  
   642  const _formatLocation = (location) => {
   643    if (location && location.latitude !== undefined && location.longitude !== undefined) {
   644      let latitude = parsing.toFloat(location.latitude)
   645      let longitude = parsing.toFloat(location.longitude)
   646      return `${latitude}, ${longitude}`
   647    } else {
   648      return 'Unknown'
   649    }
   650  }
   651  
   652  const _formatTemp = (temp) => {
   653    if (temp !== undefined && temp !== null) {
   654      return `${parsing.toFloat(temp)} °C`
   655    }
   656  
   657    return 'Unknown'
   658  }
   659  
   660  const _formatTimestamp = (sec) => {
   661    if (!sec) {
   662      sec = Date.now() / 1000
   663    }
   664    return moment.unix(sec).format('YYYY-MM-DD')
   665  }
   666  
   667  const _loadData = (recordId, state) => {
   668    let publicKey = api.getPublicKey()
   669    return api.get(`records/${recordId}`)
   670    .then(record =>
   671      Promise.all([
   672        record,
   673        api.get('agents')]))
   674    .then(([record, agents, owner, custodian]) => {
   675      state.record = record
   676      state.agents = agents.filter((agent) => agent.key !== publicKey)
   677      state.owner = agents.find((agent) => agent.key === record.owner)
   678      state.custodian = agents.find((agent) => agent.key === record.custodian)
   679    })
   680  }
   681  
   682  const _submitProposal = (record, role, publicKey) => {
   683    let transferPayload = payloads.createProposal({
   684      recordId: record.recordId,
   685      receivingAgent: publicKey,
   686      role: role
   687    })
   688  
   689    return transactions.submit([transferPayload], true).then(() => {
   690      console.log('Successfully submitted proposal')
   691    })
   692  }
   693  
   694  const _answerProposal = (record, publicKey, role, response) => {
   695    let answerPayload = payloads.answerProposal({
   696      recordId: record.recordId,
   697      receivingAgent: publicKey,
   698      role,
   699      response
   700    })
   701  
   702    return transactions.submit([answerPayload], true).then(() => {
   703      console.log('Successfully submitted answer')
   704    })
   705  }
   706  
   707  const _updateProperty = (record, value) => {
   708    let updatePayload = payloads.updateProperties({
   709      recordId: record.recordId,
   710      properties: [value]
   711    })
   712  
   713    return transactions.submit([updatePayload], true).then(() => {
   714      console.log('Successfully submitted property update')
   715    })
   716  }
   717  
   718  const _finalizeRecord = (record) => {
   719    let finalizePayload = payloads.finalizeRecord({
   720      recordId: record.recordId
   721    })
   722  
   723    return transactions.submit([finalizePayload], true).then(() => {
   724      console.log('finalized')
   725    })
   726  }
   727  
   728  const _authorizeReporter = (record, reporterKey, properties) => {
   729    let authroizePayload = payloads.createProposal({
   730      recordId: record.recordId,
   731      receivingAgent: reporterKey,
   732      role: payloads.createProposal.enum.REPORTER,
   733      properties: properties
   734    })
   735  
   736    return transactions.submit([authroizePayload], true).then(() => {
   737      console.log('Successfully submitted proposal')
   738    })
   739  }
   740  
   741  const _revokeAuthorization = (record, reporterKey, properties) => {
   742    let revokePayload = payloads.revokeReporter({
   743      recordId: record.recordId,
   744      reporterId: reporterKey,
   745      properties
   746    })
   747  
   748    return transactions.submit([revokePayload], true).then(() => {
   749      console.log('Successfully revoked reporter')
   750    })
   751  }
   752  
   753  module.exports = FishDetail