Merge pull request #35 from vishal-motwani/addformfactor

merging addFormFactor with main
This commit is contained in:
C Seymour 2018-03-16 12:38:32 -07:00 коммит произвёл GitHub
Родитель a261f8b67e 7a9c47728a
Коммит a6f3e170e5
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
40 изменённых файлов: 925 добавлений и 373 удалений

3
.gitignore поставляемый
Просмотреть файл

@ -43,3 +43,6 @@ node_modules
/nginx/*.key
/lib/*.js
# for mac DS_Store
.DS_Store

Просмотреть файл

@ -1,3 +1,12 @@
FROM heroku/nodejs
FROM node:8.9.3
RUN npm run build
# Copy package.json and package-lock.json then install deps
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
# Bundle app source
COPY . .
RUN npm run build
CMD ["npm", "start"]

Просмотреть файл

@ -47,10 +47,10 @@ All of the instructions below assume that you are within the root of the project
```sh
heroku login
```
1. The Docker add-on must be installed
1. Log into the Heroku Container Registry
```sh
heroku plugins:install heroku-docker
heroku container:login
```
1. The Heroku application must be created
@ -60,7 +60,7 @@ All of the instructions below assume that you are within the root of the project
1. Deploy the application
```sh
heroku docker:release
heroku container:push web
```
### (Optional) Set the Default Login URL and Consumer Key

4
app.js
Просмотреть файл

@ -88,9 +88,9 @@ function httpError(exception, socket) {
var app = express();
// use Jade view templates
// use Pug view templates
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.set('view engine', 'pug');
// make JSON bodies and cookies available in req objects
app.use(bodyParser.urlencoded({

Просмотреть файл

@ -33,7 +33,7 @@ module.exports = {
defaultConsumerKey: process.env.CONSUMER_KEY,
// API version
currentVersion: 41,
currentVersion: 42,
proxySettings: proxySettings
};

Просмотреть файл

@ -148,9 +148,12 @@ export const finishedRecordDelete = () => {
}
}
export const editRecord = () => {
export const editRecord = (creds, apiName, recordType) => {
return {
type: 'EDIT_RECORD'
type: 'EDIT_RECORD',
creds,
apiName,
recordType
}
}
@ -162,6 +165,25 @@ export const updateFieldValue = (field, value) => {
}
}
export const updateDepGraphFieldValue = (field, value, editValues, picklists, modalFields, fieldTree) => {
return {
type: 'UPDATE_DEP_GRAPH_FIELD_VALUE',
field,
value,
picklists,
modalFields,
editValues,
fieldTree
}
}
export const updatePicklistFields = (picklistFields) => {
return {
type: 'UPDATE_PICKLIST_FIELDS',
picklistFields
}
}
export const recordUpdateSuccess = (recordData) => {
return {
type: 'RECORD_UPDATE_SUCCESS',
@ -169,22 +191,41 @@ export const recordUpdateSuccess = (recordData) => {
}
}
export const fetchPicklist = (creds, url) => {
export const fetchPicklists = (creds, apiName, recordType) => {
return {
type: 'FETCH_PICKLIST',
type: 'FETCH_PICKLISTS',
creds,
url
apiName,
recordType
}
}
export const receivePicklist = (url, result) => {
export const receivePicklists = (url, result) => {
return {
type: 'RECEIVE_PICKLIST',
type: 'RECEIVE_PICKLISTS',
url,
result
}
}
export const editDepGraph = (picklists, modalFields, editValues, fieldTree, prevMode) => {
return {
type: 'EDIT_DEP_GRAPH',
picklists,
modalFields,
editValues,
fieldTree,
prevMode
}
}
export const closeDepGraph = (mode) => {
return {
type: 'CLOSE_DEP_GRAPH',
mode
}
}
export const showError = (response, responseJson) => {
return {
type: 'SHOW_ERROR',

Просмотреть файл

@ -14,10 +14,10 @@ const CreateableEntitiesList = ({onChange, entities}) => {
onChange={(event) => {if (event.target.value != "") {onChange(event.target.value)}}}>
[
<option key="" value="">Select an object...</option>
{ entities.sobjects.filter(x => x.createable).map((sobject) => {
{ Object.keys(entities.sobjects).map(function (key) {
return <option
key={sobject.name}
value={sobject.name}>{sobject.label}</option>
key={entities.sobjects[key].apiName}
value={entities.sobjects[key].apiName}>{entities.sobjects[key].label}</option>
})}
]
</select>

Просмотреть файл

@ -0,0 +1,98 @@
import React, { PropTypes } from 'react'
import RecordButton from './RecordButton'
import {ModalContainer, ModalDialog} from 'react-modal-dialog'
function createNodeMap(fieldTree, nodeMap = [], size = 0 ) {
for (var property in fieldTree) {
nodeMap.push({property, size});
createNodeMap(fieldTree[property], nodeMap, size + 1);
}
return nodeMap;
}
function getEditorNodes(fieldTree, fieldComponents, fieldLabels){
const nodeArray = createNodeMap(fieldTree);
const nodeElArray = [];
nodeArray.forEach((value, key, nodeArray) => {
var divStyle = {
paddingLeft: ''+ 100 * value.size + 'px'
};
var nodeEl = <div key={"depGraphEdit" + value.property} style={divStyle}>{fieldLabels[value.property]} : {fieldComponents[value.property]}</div>
nodeElArray.push(nodeEl);
});
return nodeElArray;
}
// Dependency graph editor
const DepGraphEditor = ({depGraph, picklists, editValues, onFieldValueUpdate, onClose}) => {
let fieldComponents = {}
let fieldLabels = {}
depGraph.modalFields.map(
(field) => {
if (field.dataType == "Picklist") {
fieldComponents[field.apiName] =
<select
key={'depGraph' + field.apiName}
value={editValues[field.apiName].current}
onChange={(event) => onFieldValueUpdate(
field.apiName,
event.target.value,
editValues,
picklists,
depGraph.modalFields,
depGraph.fieldTree)}>
{ depGraph.picklistFields.get(field.apiName).map((picklistValue) => {
return (
<option
key={picklistValue.value}
value={picklistValue.value}>{picklistValue.label}</option>)
})}
</select>
} else if (field.dataType == "Boolean" ) {
fieldComponents[field.apiName] =
<input
type="checkbox"
className="fieldEdit"
key={'depGraph' + field.apiName}
name={field.apiName}
value={editValues[field.apiName].current}
onChange={(event) => onFieldValueUpdate(
field.apiName,
new Boolean(!editValues[field.apiName].current).valueOf(),
editValues,
picklists,
depGraph.modalFields,
depGraph.fieldTree)}
data-datatype={field.dataType}/>
}
fieldLabels[field.apiName] = field.label
}
);
return (
<div key="depGraphContainer">
<ModalContainer >
<ModalDialog>
<div key="depGraphNodes">
{ getEditorNodes(depGraph.fieldTree, fieldComponents, fieldLabels) }
</div>
<div className="slds-p-left--medium slds-p-top--small slds-p-botom--medium" key="depGraphCloseButtonDiv">
<RecordButton key="depGraphCloseButton" label='Close' onClick={onClose} />
</div>
</ModalDialog>
</ModalContainer>
</div>
)
}
DepGraphEditor.propTypes = {
depGraph: PropTypes.object.isRequired,
picklists: PropTypes.object.isRequired,
editValues: PropTypes.object.isRequired,
onClose: PropTypes.func.isRequired,
onFieldValueUpdate: PropTypes.func.isRequired
}
export default DepGraphEditor

Просмотреть файл

@ -2,47 +2,68 @@ import React, { PropTypes } from 'react'
import RecordSection from './RecordSection'
import RecordButton from './RecordButton'
import DepGraphEditor from './DepGraphEditor'
// Component that displays a Record.
const Record = ({creds, error, layoutMode, uiMode, recordView, picklists, onBackClick, onCloneClick, onDeleteClick, onEditClick, onSaveClick, onSaveNewClick, onFieldValueUpdate, onFetchPicklist}) => {
const Record = ({creds, error, layoutMode, uiMode, prevMode, recordView, picklists, depGraph, onBackClick, onCloneClick, onDeleteClick, onEditClick, onSaveClick, onSaveNewClick, onFieldValueUpdate, onDepGraphFieldValueUpdate, onDepGraphClose, onEditDepGraph}) => {
let recordType = '012000000000000AAA'; // 'Master'
if (recordView.record && recordView.record.recordTypeInfo) {
recordType = recordView.record.recordTypeInfo.recordTypeId;
}
if (layoutMode == "Clone"){
layoutMode = "Edit"
}
// TODO: support layouts other than Full.
let allowEdit = (uiMode !== 'View');
return (
<div>
<table className="slds-table" style={{"width":"auto"}}>
{recordView.layouts.Full[layoutMode].map((section, i) =>
<RecordSection
allowEdit={allowEdit}
key={'section' + i}
onFieldValueUpdate={onFieldValueUpdate}
onFetchPicklist={(url) => onFetchPicklist(creds, url)}
picklists={picklists}
error={error}
editValues={recordView.editValues}
section={section}
index={i} />
)}
</table>
<div className="slds-p-left--medium slds-p-top--small slds-p-botom--medium">
<RecordButton label='Back' onClick={onBackClick} />
{ uiMode === 'View' &&
<RecordButton label='Delete' onClick={() => onDeleteClick(creds, recordView.recordId)} />
}
{ uiMode === 'View' &&
<RecordButton label='Edit' onClick={() => onEditClick(creds, recordView.recordId)} />
}
{ uiMode === 'View' &&
<RecordButton label='Clone' onClick={() => onCloneClick(creds, recordView.recordId)} />
}
{ uiMode === 'Edit' &&
<RecordButton label='Save' onClick={() => onSaveClick(creds, recordView.recordId, recordView.objectInfo, recordView.editValues)} />
}
{ (uiMode === 'Create' || uiMode === 'Clone') &&
<RecordButton label='Save' onClick={() => onSaveNewClick(creds, recordView.apiName, recordView.objectInfo, recordView.editValues)} />
}
</div>
<div>
{uiMode === 'EditDepGraph' &&
<DepGraphEditor
depGraph={depGraph}
picklists={picklists}
editValues={recordView.editValues}
onFieldValueUpdate={onDepGraphFieldValueUpdate}
onClose={() => onDepGraphClose(prevMode)}/> }
<table className="slds-table" style={{"width":"auto"}}>
{recordView.layouts.Full[layoutMode].map((section, i) =>
<RecordSection
allowEdit={allowEdit}
uiMode={uiMode}
key={'section' + i}
onFieldValueUpdate={onFieldValueUpdate}
picklists={picklists}
error={error}
editValues={recordView.editValues}
section={section}
index={i}
objectInfo={recordView.objectInfo}
onEditDepGraph={onEditDepGraph}
recordView={recordView}/>
)}
</table>
<div className="slds-p-left--medium slds-p-top--small slds-p-botom--medium">
<RecordButton label='Back' onClick={onBackClick} />
{ uiMode === 'View' &&
<RecordButton label='Delete' onClick={() => onDeleteClick(creds, recordView.recordId)} />
}
{ uiMode === 'View' &&
<RecordButton label='Edit' onClick={() => onEditClick(creds, recordView.objectInfo.apiName, recordType)} />
}
{ uiMode === 'View' &&
<RecordButton label='Clone' onClick={() => onCloneClick(creds, recordView.recordId, recordView.record.apiName, recordType)} />
}
{ uiMode === 'Edit' &&
<RecordButton label='Save' onClick={() => onSaveClick(creds, recordView.recordId, recordView.objectInfo, recordView.editValues)} />
}
{ (uiMode === 'Create' || uiMode === 'Clone') &&
<RecordButton label='Save' onClick={() => onSaveNewClick(creds, recordView.apiName, recordView.objectInfo, recordView.editValues)} />
}
</div>
)
</div> )
}
// layoutMode = the mode of the layout to use. for Clone this will be 'Edit'.
@ -51,9 +72,11 @@ Record.propTypes = {
error: PropTypes.object.isRequired,
creds: PropTypes.object.isRequired,
layoutMode: PropTypes.string.isRequired,
prevMode: PropTypes.string,
uiMode: PropTypes.string.isRequired,
recordView: PropTypes.object.isRequired,
picklists: PropTypes.object.isRequired,
picklists : PropTypes.object,
depGraph: PropTypes.object,
onBackClick: PropTypes.func.isRequired,
onCloneClick: PropTypes.func.isRequired,
onDeleteClick: PropTypes.func.isRequired,
@ -61,7 +84,9 @@ Record.propTypes = {
onSaveClick: PropTypes.func.isRequired,
onSaveNewClick: PropTypes.func.isRequired,
onFieldValueUpdate: PropTypes.func.isRequired,
onFetchPicklist: PropTypes.func.isRequired
onDepGraphFieldValueUpdate: PropTypes.func.isRequired,
onEditDepGraph: PropTypes.func.isRequired,
onDepGraphClose: PropTypes.func.isRequired
}
export default Record

Просмотреть файл

@ -46,33 +46,85 @@ const getViewItemCells = (item, itemLabel, nonEmptyItem) => {
}
}
const getEditComponent = (component, picklists, onFetchPicklist, onFieldValueUpdate, editValues, itemLabel, i) => {
if (component.picklistUrl) {
let value = editValues[component.field].current;
if (value == null) {
value = "";
}
const getFlattenedTree = (objectInfo, fieldTree, rootField) => {
const flattenedTree = []
flattenedTree.push(objectInfo.fields[rootField])
doFlattenTree(objectInfo, fieldTree, flattenedTree)
return flattenedTree
}
let picklistValues = [];
let hasPicklistValues = false;
if (picklists[component.picklistUrl]) {
picklistValues = picklists[component.picklistUrl].values;
hasPicklistValues = true;
const doFlattenTree = (objectInfo, tree, flattenedTree) => {
if (tree) {
let treeKeys = Object.keys(tree)
for (var treeKey in treeKeys){
flattenedTree.push(objectInfo.fields[treeKeys[treeKey]])
doFlattenTree(objectInfo, tree[treeKeys[treeKey]], flattenedTree)
}
}
}
const getEditComponent = (component, picklists, onFieldValueUpdate, editValues, itemLabel, i, objectInfo, onEditDepGraph, uiMode, recordView) => {
let isPicklist = (component.fieldInfo && component.fieldInfo.dataType == 'Picklist');
let currPicklistValue = "";
let picklistValues = [];
if (isPicklist) {
currPicklistValue = editValues[component.field].current;
if (currPicklistValue == null) {
currPicklistValue = "";
}
if (picklists.fieldValues && picklists.fieldValues[component.field]) {
picklistValues = picklists.fieldValues[component.field].values;
} else {
// picklist collection fetch has not completed yet, or the field is somehow missing from its results.
// add the current value so that it shows nicely in the UI.
picklistValues.push({value: component.value, label: component.displayValue})
}
}
if (objectInfo
&& ((component.field && component.field in objectInfo.dependentFields)
|| (component.fieldInfo && component.fieldInfo.controllingFields.length > 0))) {
// last field in controlling fields is the root field
let lastControllingIndex = component.fieldInfo.controllingFields.length - 1
let rootField = null
if (component.field in objectInfo.dependentFields){
rootField = component.field
} else {
rootField = component.fieldInfo.controllingFields[lastControllingIndex]
}
// retrieve the picklist fields that need to show up
const subfieldTree = objectInfo.dependentFields[rootField]
const modalFields = getFlattenedTree(objectInfo, subfieldTree, rootField)
// open a modal on click of input field
let fieldTree = {}
fieldTree[rootField] = subfieldTree
return (
<div>
<label
key={'componentInput' + itemLabel + ',' + i}
onClick={(event) => onEditDepGraph(picklists, modalFields, editValues, fieldTree, uiMode.toString())}
value={currPicklistValue}>
{editValues[component.field].current}
</label>
<button className="fa fa-pencil"
key={'componentInput' + itemLabel + 'button,' + i}
onClick={(event) => onEditDepGraph(picklists, modalFields, editValues, fieldTree, uiMode.toString())}>
</button>
</div>
);
}
if (isPicklist) {
return (
<select
key={component.picklistUrl}
value={value}
onChange={(event) => onFieldValueUpdate(component.field, event.target.value)}
onFocus={(event) => {
if (!hasPicklistValues) {
onFetchPicklist(component.picklistUrl);
}
}} >
value={currPicklistValue}
onChange={(event) => onFieldValueUpdate(component.field, event.target.value)}>
{ picklistValues.map((picklistValue) => {
return <option
key={picklistValue.value}
@ -86,10 +138,14 @@ const getEditComponent = (component, picklists, onFetchPicklist, onFieldValueUpd
if (currentVal != null) {
currentValStr = currentVal.toString();
}
let componentType = 'text'
if(component && component.fieldInfo.dataType == "Boolean") {
componentType = 'checkbox'
currentValStr = currentVal;
}
return (
<input
type="text"
type={componentType}
className="fieldEdit"
name={component.field}
value={currentValStr}
@ -101,7 +157,7 @@ const getEditComponent = (component, picklists, onFetchPicklist, onFieldValueUpd
}
}
const getEditItemCells = (item, picklists, onFetchPicklist, onFieldValueUpdate, error, editValues, itemLabel, nonEmptyItem) => {
const getEditItemCells = (item, picklists, onFieldValueUpdate, error, editValues, itemLabel, nonEmptyItem, objectInfo, onEditDepGraph, uiMode, recordView) => {
return (
<td className="slds-cell-wrap" key={'editItemCell' + itemLabel} style={{"maxWidth":"350px"}}>
{ item.customLinkUrl &&
@ -119,7 +175,7 @@ const getEditItemCells = (item, picklists, onFetchPicklist, onFieldValueUpdate,
<span className="slds-required">*</span>
}
{ component.editableForUpdate &&
getEditComponent(component, picklists, onFetchPicklist, onFieldValueUpdate, editValues, itemLabel, i)
getEditComponent(component, picklists, onFieldValueUpdate, editValues, itemLabel, i, objectInfo, onEditDepGraph, uiMode, recordView)
}
{ !component.editableForUpdate &&
<label key={'component' + itemLabel + ',' + i}>{component.displayValue}</label>
@ -144,8 +200,7 @@ const getEditItemCells = (item, picklists, onFetchPicklist, onFieldValueUpdate,
// Component that displays a Record row. May include multiple items. JSX doesn't allow us to return
// fragments made up of multiple <td>s without wrapping them in a single element so we can't break out
// RecordItem into a separate component.
const RecordRow = ({row, allowEdit, error, editValues, picklists, onFetchPicklist, onFieldValueUpdate, sectionIndex, rowIndex}) => {
const RecordRow = ({row, allowEdit, error, editValues, picklists, onFieldValueUpdate, sectionIndex, rowIndex, objectInfo, onEditDepGraph, uiMode, recordView}) => {
let rowLabel = sectionIndex + ',' + rowIndex;
return (
@ -156,7 +211,7 @@ const RecordRow = ({row, allowEdit, error, editValues, picklists, onFetchPicklis
if (!allowEdit) {
return getViewItemCells(item, itemLabel, nonEmptyItem);
} else {
return getEditItemCells(item, picklists, onFetchPicklist, onFieldValueUpdate, error, editValues, itemLabel, nonEmptyItem);
return getEditItemCells(item, picklists, onFieldValueUpdate, error, editValues, itemLabel, nonEmptyItem, objectInfo, onEditDepGraph, uiMode, recordView);
}
})}
</tr>
@ -168,11 +223,14 @@ RecordRow.propTypes = {
error: PropTypes.object.isRequired,
allowEdit: PropTypes.bool.isRequired,
editValues: PropTypes.object.isRequired,
picklists: PropTypes.object.isRequired,
picklists: PropTypes.object,
sectionIndex: PropTypes.number.isRequired,
rowIndex: PropTypes.number.isRequired,
onFieldValueUpdate: PropTypes.func.isRequired,
onFetchPicklist: PropTypes.func.isRequired
objectInfo: PropTypes.object.isRequired,
onEditDepGraph: PropTypes.func.isRequired,
uiMode: PropTypes.string.isRequired,
recordView: PropTypes.object.isRequired
}
export default RecordRow
export default RecordRow

Просмотреть файл

@ -4,7 +4,7 @@ import ReactDOM from 'react-dom';
import RecordRow from './RecordRow';
// Component that displays a Record section.
const RecordSection = ({section, error, editValues, picklists, onFieldValueUpdate, onFetchPicklist, allowEdit, index}) => {
const RecordSection = ({section, error, editValues, picklists, onFieldValueUpdate, allowEdit, index, objectInfo, onEditDepGraph, uiMode, recordView}) => {
return (
<tbody>
@ -19,14 +19,17 @@ const RecordSection = ({section, error, editValues, picklists, onFieldValueUpdat
<RecordRow
key={'sectionRow' + index + ',' + i}
allowEdit={allowEdit}
uiMode={uiMode}
picklists={picklists}
onFieldValueUpdate={onFieldValueUpdate}
onFetchPicklist={onFetchPicklist}
error={error}
editValues={editValues}
row={row}
sectionIndex={index}
rowIndex={i} />
rowIndex={i}
objectInfo={objectInfo}
recordView={recordView}
onEditDepGraph={onEditDepGraph}/>
)}
</tbody>
);
@ -35,12 +38,15 @@ const RecordSection = ({section, error, editValues, picklists, onFieldValueUpdat
RecordSection.propTypes = {
error: PropTypes.object.isRequired,
section: PropTypes.object.isRequired,
objectInfo: PropTypes.object.isRequired,
editValues: PropTypes.object.isRequired,
picklists: PropTypes.object.isRequired,
picklists: PropTypes.object,
uiMode: PropTypes.string.isRequired,
allowEdit: PropTypes.bool.isRequired,
index: PropTypes.number.isRequired,
onFieldValueUpdate: PropTypes.func.isRequired,
onFetchPicklist: PropTypes.func.isRequired
onEditDepGraph: PropTypes.func.isRequired,
recordView: PropTypes.object.isRequired,
}
export default RecordSection

Просмотреть файл

@ -1,6 +1,6 @@
import React, { PropTypes } from 'react'
import JSONPretty from 'react-json-pretty';
import Inspector from 'react-json-inspector';
import AsyncKickoff from '../containers/AsyncKickoff'
import CreateableEntitiesList from './CreateableEntitiesList'
import RecentItemList from './RecentItemList'
@ -30,7 +30,7 @@ let getFooter = (screen, error, rawjson) => {
{ errorNode }
<RecordButton key="showJsonButton" label='Show JSON' onClick={() => $(".raw").toggle()} />
<div className="raw" style={{"display":"none"}}>
<JSONPretty key="rawjson" json={rawjson} />
<Inspector key="rawjson" data={rawjson} />
</div>
</div>
);
@ -39,42 +39,55 @@ let getFooter = (screen, error, rawjson) => {
<div className="slds-p-left--medium slds-p-top--small slds-p-botom--medium">
<RecordButton key="showJsonButton" label='Show JSON' onClick={() => $(".raw").toggle()} />
<div className="raw" style={{"display":"none"}}>
<JSONPretty key="rawjson" json={rawjson} />
<Inspector key="rawjson" data={rawjson} />
</div>
</div>
);
}
}
// Component that displays login / recent items / record screens.
const RecordViewer = ({screen, updateEntities, updateItems, creds, error, record, recordId, headerRecordId, rawjson, mode, context, picklists, entities, recentItems, onFormFactorSelect, onRecordIdUpdate, onViewRecordClick, onNewRecordClick, onCloneClick, onRecordClick, onBackClick, onDeleteClick, onEditClick, onSaveClick, onSaveNewClick, onFieldValueUpdate, onFetchEntities, onFetchPicklist, onFetchRecord, onFetchRecentItems}) => {
const RecordViewer = ({screen, updateEntities, updateItems, creds, error, record, recordId, headerRecordId, rawjson, mode, context, picklists, depGraph, entities, recentItems, onFormFactorSelect, onRecordIdUpdate, onViewRecordClick, onNewRecordClick, onCloneClick, onRecordClick, onBackClick, onDeleteClick, onEditClick, onSaveClick, onSaveNewClick, onFieldValueUpdate, onDepGraphFieldValueUpdate, onFetchEntities, onFetchPicklist, onFetchRecord, onFetchRecentItems, onEditDepGraph, onDepGraphClose, prevMode}) => {
if (screen == 'RECORD') {
let layoutMode = (mode === 'Clone') ? 'Edit' : mode;
let layoutMode;
if (mode === 'EditDepGraph') {
layoutMode = prevMode;
} else if (mode === 'Clone') {
layoutMode = 'Edit';
} else {
layoutMode = mode;
}
return (
<div>
<RecordHeader formFactor={context.formFactor}
recordId={headerRecordId}
onFormFactorSelect={onFormFactorSelect}
onRecordIdUpdate={onRecordIdUpdate}
onViewRecordClick={(newRecordId) => onViewRecordClick(creds, newRecordId, context)}/>
{layoutMode == "View" &&
<RecordHeader formFactor={context.formFactor}
recordId={headerRecordId}
onFormFactorSelect={onFormFactorSelect}
onRecordIdUpdate={onRecordIdUpdate}
onViewRecordClick={(newRecordId) => onViewRecordClick(creds, newRecordId, context)}/>
}
<Record recordView={record}
layoutMode={layoutMode}
uiMode={mode}
prevMode={prevMode}
creds={creds}
error={error}
picklists={picklists}
depGraph={depGraph}
onBackClick={onBackClick}
onCloneClick={(credsIn, recordIdIn) => onCloneClick(credsIn, recordIdIn, context)}
onCloneClick={(credsIn, recordIdIn, apiNameIn, recordTypeIn) => onCloneClick(credsIn, recordIdIn, apiNameIn, recordTypeIn, context)}
onDeleteClick={onDeleteClick}
onEditClick={onEditClick}
onSaveClick={onSaveClick}
onSaveNewClick={onSaveNewClick}
onFieldValueUpdate={onFieldValueUpdate}
onFetchPicklist={onFetchPicklist}/>
onEditDepGraph={onEditDepGraph}
onDepGraphFieldValueUpdate={onDepGraphFieldValueUpdate}
onDepGraphClose={onDepGraphClose} />
{ getFooter(screen, error, rawjson) }
</div>
);
} else if (screen == 'FETCH_RECORD') {
return (
<div>
@ -134,6 +147,7 @@ RecordViewer.propTypes = {
onFormFactorSelect: PropTypes.func.isRequired,
onRecordIdUpdate: PropTypes.func.isRequired,
onViewRecordClick: PropTypes.func.isRequired,
depGraph: PropTypes.object,
onRecordClick: PropTypes.func.isRequired,
onBackClick: PropTypes.func.isRequired,
onCloneClick: PropTypes.func.isRequired,
@ -145,8 +159,10 @@ RecordViewer.propTypes = {
onFetchEntities: PropTypes.func.isRequired,
onFetchRecentItems: PropTypes.func.isRequired,
onFieldValueUpdate: PropTypes.func.isRequired,
onFetchPicklist: PropTypes.func.isRequired,
onFetchRecord: PropTypes.func.isRequired
onDepGraphFieldValueUpdate: PropTypes.func.isRequired,
onDepGraphClose: PropTypes.func.isRequired,
onFetchRecord: PropTypes.func.isRequired,
onEditDepGraph: PropTypes.func.isRequired
}
export default RecordViewer

Просмотреть файл

@ -14,8 +14,10 @@ const mapStateToProps = (state) => {
headerRecordId: state.header.recordId,
mode: state.record.mode,
context: state.context,
prevMode: state.record.prevMode,
creds: state.login,
picklists: state.picklists,
picklists : state.picklists,
depGraph: state.depGraph,
rawjson: state.rawjson,
error: state.error
}
@ -67,15 +69,19 @@ const mapDispatchToProps = (dispatch) => {
},
onNewRecordClick: (creds, apiName, context) => {
dispatch(actions.fetchCreateDefaults(creds, apiName, context))
// hacky: default record type for now
dispatch(actions.fetchPicklists(creds, apiName, '012000000000000AAA'))
},
onCloneClick: (creds, id, context) => {
onCloneClick: (creds, id, apiName, recordType, context) => {
dispatch(actions.fetchCloneDefaults(creds, id, context))
dispatch(actions.fetchPicklists(creds, apiName, recordType))
},
onDeleteClick: (creds, id) => {
dispatch(actions.deleteRecord(creds, id))
},
onEditClick: () => {
dispatch(actions.editRecord())
onEditClick: (creds, apiName, recordType) => {
dispatch(actions.editRecord(creds, apiName, recordType))
dispatch(actions.fetchPicklists(creds, apiName, recordType))
},
onSaveClick: (creds, id, objectInfo, editValues) => {
dispatch(actions.saveRecord(creds, id, objectInfo, editValues))
@ -95,8 +101,14 @@ const mapDispatchToProps = (dispatch) => {
onFieldValueUpdate: (field, value) => {
dispatch(actions.updateFieldValue(field, value))
},
onFetchPicklist: (creds, url) => {
dispatch(actions.fetchPicklist(creds, url));
onEditDepGraph: (picklists, modalFields, editValues, fieldTree, prevMode) => {
dispatch(actions.editDepGraph(picklists, modalFields, editValues, fieldTree, prevMode));
},
onDepGraphFieldValueUpdate: (field, value, picklists, modalFields, rootField, fieldTree) => {
dispatch(actions.updateDepGraphFieldValue(field, value, picklists, modalFields, rootField, fieldTree))
},
onDepGraphClose: (mode) => {
dispatch(actions.closeDepGraph(mode))
}
}
}

Просмотреть файл

@ -0,0 +1,59 @@
function getParentToChildPicklistValues(picklists, modalFields, editValues){
// create a new map to fill in
const picklistCollectionMap = new Map()
for (let modalFieldIndex in modalFields){
const field = modalFields[modalFieldIndex]
const picklistValue = picklists.fieldValues[modalFields[modalFieldIndex].apiName]
const individualValueObjects = getLegalValues(picklists, field, editValues)
picklistCollectionMap.set(field.apiName,individualValueObjects)
}
return picklistCollectionMap
}
function getLegalValues(picklists, field, editValues){
const picklistValue = picklists.fieldValues[field.apiName]
if (picklistValue){
if (isEmpty(picklistValue.controllerValues)){
return picklistValue.values.map(v => ({value: v.value, label: v.label}));
} else {
// fill in picklistFields
const individualValueObjects = [];
const parentField = field.controllerName
let parentFieldValue = editValues[parentField].current
if (parentFieldValue != null){
parentFieldValue = parentFieldValue.toString()
}
let validForIndex = -1
if (picklistValue.controllerValues[parentFieldValue] != null){
validForIndex = picklistValue.controllerValues[parentFieldValue]
}
picklistValue.values.forEach((individualValue) => {
if (isInArray(validForIndex,individualValue.validFor)){
individualValueObjects.push({
value: individualValue.value,
label: individualValue.label
});
}
});
return individualValueObjects
}
} else {
return []
}
}
function isEmpty(obj) {
for(var key in obj) {
if(obj.hasOwnProperty(key))
return false;
}
return true;
}
function isInArray(value, array) {
return array.indexOf(value) > -1;
}
export default { getParentToChildPicklistValues, getLegalValues }

Просмотреть файл

@ -1,220 +1,220 @@
function getLayoutItemModel(objectInfo, record, recordType, item) {
var result = {};
result.label = item.label;
var values = [];
var linkId;
var linkText;
var customLinkUrl;
var customText;
(item.layoutComponents || []).forEach(function (component) {
let picklistUrl = undefined;
// Component display info.
if (component.componentType == 'Field') {
var compValue = component.apiName;
var fieldInfo = objectInfo.fields[compValue];
// Picklist value URL.
if (fieldInfo && fieldInfo.dataType == 'Picklist') {
picklistUrl = '/services/data/v41.0/ui-api/object-info/' + objectInfo.apiName + '/picklist-values/' + recordType + '/' + fieldInfo.apiName;
}
// Reference link.
if (fieldInfo && fieldInfo.reference) {
// The relationship may be absent if it amounts to null.
if (record.fields[fieldInfo.relationshipName]) {
var relatedData = record.fields[fieldInfo.relationshipName].value
if (relatedData) {
linkId = relatedData.fields.Id.value
linkText = relatedData.fields.Name.value
var result = {};
result.label = item.label;
var values = [];
var linkId;
var linkText;
var customLinkUrl;
var customText;
(item.layoutComponents || []).forEach(function (component) {
// Component display info.
if (component.componentType == 'Field') {
var compValue = component.apiName;
var fieldInfo = objectInfo.fields[compValue];
// Reference link.
if (fieldInfo && fieldInfo.reference) {
// The relationship may be absent if it amounts to null.
if (record.fields[fieldInfo.relationshipName]) {
var relatedData = record.fields[fieldInfo.relationshipName].value
if (relatedData) {
linkId = relatedData.fields.Id.value
linkText = relatedData.fields.Name.value
}
}
}
}
if (fieldInfo && (fieldInfo.type == 'Datetime' || fieldInfo.type == 'DateOnly')) {
var currValue = record.fields[compValue].value;
var formattedData = new Date(currValue);
values.push(
{displayValue: formattedData,
value: currValue,
label:component.label,
field:compValue,
fieldInfo,
picklistUrl,
editableForNew:item.editableForNew,
editableForUpdate:item.editableForUpdate,
isNull:currValue == null});
} else if (record.fields[compValue]) {
var displayValue = record.fields[compValue].displayValue;
let rawValue = record.fields[compValue].value;
if (displayValue == null && rawValue != null) {
displayValue = rawValue.toString();
}
values.push(
{displayValue: displayValue,
value: rawValue,
label:component.label,
field:compValue,
fieldInfo,
picklistUrl,
editableForNew:item.editableForNew,
editableForUpdate:item.editableForUpdate,
isNull:displayValue == null})
} else {
console.log('Missing expected field: ' + compValue);
}
} else if (component.componentType == 'CustomLink') {
customLinkUrl = component.customLinkUrl;
linkText = component.label;
} else if (component.componentType == 'Canvas') {
customText = 'Canvas: ' + component.apiName;
} else if (component.componentType == 'EmptySpace') {
customText = '';
} else if (component.componentType == 'VisualforcePage') {
customText = 'VF Page: ' + component.apiName;
} else if (component.componentType == 'ReportChart') {
customText = 'Report Chart: ' + component.apiName;
}
});
result.values = values;
result.linkId = linkId;
result.linkText = linkText;
result.customLinkUrl = customLinkUrl;
result.customText = customText;
return result;
}
function getLayoutRowModel(objectInfo, record, recordType, itemsIn) {
var items = [];
(itemsIn || []).forEach(function (item) {
items.push(getLayoutItemModel(objectInfo, record, recordType, item));
});
var result = {
items: items
};
return result;
}
function getLayoutSectionModel(objectInfo, record, recordType, section) {
var result = {};
result.heading = section.heading;
result.useHeading = (section.useHeading) ? section.useHeading : false;
var rows = [];
(section.layoutRows || []).forEach(function (row) {
rows.push(getLayoutRowModel(objectInfo, record, recordType, row.layoutItems));
});
result.rows = rows;
return result;
}
function getLayoutModelForDefaults(defaults) {
let objectInfo = defaults.objectInfo;
let record = defaults.record;
let layout = defaults.layout;
let recordType = '012000000000000AAA'; // 'Master'
if (record.recordTypeInfo) {
recordType = record.recordTypeInfo.recordTypeId;
}
let layouts = {};
let editValues = {};
try {
let modeType = layout.mode;
let layoutType = layout.layoutType;
layouts[layoutType] = {};
const sections = layout.sections.map((section) => getLayoutSectionModel(objectInfo, record, recordType, section));
sections.forEach((section) =>
section.rows.forEach((row) =>
row.items.forEach((item) =>
item.values.forEach((value) =>
editValues[value.field] = {
original: value.value,
current: value.value
}))));
layouts[layoutType][modeType] = sections;
let result = {
layouts,
editValues,
objectInfo,
apiName: objectInfo.apiName
};
return result;
} catch (err) {
console.log('ERROR CREATING DEFAULTS LAYOUT MODEL ' + err);
return {layouts: [], editValues: {}, objectInfo:{}, apiName:null};
}
}
function getLayoutModel(recordId, recordView) {
let record = recordView.records[recordId];
let apiName = record.apiName;
let entityEntry = recordView.layouts[apiName];
let objectInfo = recordView.objectInfos[apiName];
let recordType = '012000000000000AAA'; // 'Master'
if (record.recordTypeInfo) {
recordType = record.recordTypeInfo.recordTypeId;
}
let layouts = {};
let editValues = {};
try {
let recordTypeRep = entityEntry[Object.keys(entityEntry)[0]]; // TODO: support multiple record types.
for (const layoutType of Object.keys(recordTypeRep)) {
let layoutTypeRep = recordTypeRep[layoutType];
for (const modeType of Object.keys(layoutTypeRep)) {
let layoutRep = layoutTypeRep[modeType];
if (!layouts[layoutType]) {
layouts[layoutType] = {};
if (fieldInfo && (fieldInfo.type == 'Datetime' || fieldInfo.type == 'DateOnly')) {
var currValue = record.fields[compValue].value;
var formattedData = new Date(currValue);
values.push(
{displayValue: formattedData,
value: currValue,
label:component.label,
field:compValue,
fieldInfo,
editableForNew:item.editableForNew,
editableForUpdate:item.editableForUpdate,
isNull:currValue == null});
} else if (record.fields[compValue]) {
var displayValue = record.fields[compValue].displayValue;
let rawValue = record.fields[compValue].value;
if (displayValue == null && rawValue != null) {
displayValue = rawValue.toString();
}
values.push(
{displayValue: displayValue,
value: rawValue,
label:component.label,
field:compValue,
fieldInfo,
editableForNew:item.editableForNew,
editableForUpdate:item.editableForUpdate,
isNull:displayValue == null})
} else {
console.log('Missing expected field: ' + compValue);
}
const sections = layoutRep.sections.map((section) => getLayoutSectionModel(objectInfo, record, recordType, section));
if (modeType === 'Edit') {
sections.forEach((section) =>
section.rows.forEach((row) =>
row.items.forEach((item) =>
item.values.forEach((value) =>
editValues[value.field] = {
original: value.value,
current: value.value}
))));
}
layouts[layoutType][modeType] = sections;
} else if (component.componentType == 'CustomLink') {
customLinkUrl = component.customLinkUrl;
linkText = component.label;
} else if (component.componentType == 'Canvas') {
customText = 'Canvas: ' + component.apiName;
} else if (component.componentType == 'EmptySpace') {
customText = '';
} else if (component.componentType == 'VisualforcePage') {
customText = 'VF Page: ' + component.apiName;
} else if (component.componentType == 'ReportChart') {
customText = 'Report Chart: ' + component.apiName;
}
}
let result = {
layouts,
editValues,
objectInfo,
recordId: record.id
});
result.values = values;
result.linkId = linkId;
result.linkText = linkText;
result.customLinkUrl = customLinkUrl;
result.customText = customText;
return result;
}
function getLayoutRowModel(objectInfo, record, recordType, itemsIn) {
var items = [];
(itemsIn || []).forEach(function (item) {
items.push(getLayoutItemModel(objectInfo, record, recordType, item));
});
var result = {
items: items
};
return result;
} catch (err) {
console.log('ERROR CREATING LAYOUT MODEL ' + err);
return {layouts: [], editValues: {}, objectInfo:{}, recordId: null};
}
}
export default {getLayoutItemModel, getLayoutRowModel, getLayoutSectionModel, getLayoutModel, getLayoutModelForDefaults}
function getLayoutSectionModel(objectInfo, record, recordType, section) {
var result = {};
result.heading = section.heading;
result.useHeading = (section.useHeading) ? section.useHeading : false;
var rows = [];
(section.layoutRows || []).forEach(function (row) {
rows.push(getLayoutRowModel(objectInfo, record, recordType, row.layoutItems));
});
result.rows = rows;
return result;
}
function getLayoutModelForDefaults(defaults) {
let objectInfo = {}
if (defaults.objectInfo) {
// in case of clone
objectInfo = defaults.objectInfo
} else {
// in case of create
objectInfo = defaults.objectInfos[defaults.record.apiName];
}
let record = defaults.record;
let layout = defaults.layout;
let recordType = '012000000000000AAA'; // 'Master'
if (record.recordTypeInfo) {
recordType = record.recordTypeInfo.recordTypeId;
}
let layouts = {};
let editValues = {};
try {
let modeType = layout.mode;
let layoutType = layout.layoutType;
layouts[layoutType] = {};
const sections = layout.sections.map((section) => getLayoutSectionModel(objectInfo, record, recordType, section));
sections.forEach((section) =>
section.rows.forEach((row) =>
row.items.forEach((item) =>
item.values.forEach((value) =>
editValues[value.field] = {
original: value.value,
current: value.value
}))));
layouts[layoutType][modeType] = sections;
let result = {
layouts,
editValues,
objectInfo,
apiName: objectInfo.apiName
};
return result;
} catch (err) {
console.log('ERROR CREATING DEFAULTS LAYOUT MODEL ' + err);
return {layouts: [], editValues: {}, objectInfo:{}, apiName:null};
}
}
function getLayoutModel(recordId, recordView) {
let record = recordView.records[recordId];
let apiName = record.apiName;
let entityEntry = recordView.layouts[apiName];
let objectInfo = recordView.objectInfos[apiName];
let recordType = '012000000000000AAA'; // 'Master'
if (record.recordTypeInfo) {
recordType = record.recordTypeInfo.recordTypeId;
}
let layouts = {};
let editValues = {};
try {
let recordTypeRep = entityEntry[Object.keys(entityEntry)[0]]; // TODO: support multiple record types.
for (const layoutType of Object.keys(recordTypeRep)) {
let layoutTypeRep = recordTypeRep[layoutType];
for (const modeType of Object.keys(layoutTypeRep)) {
let layoutRep = layoutTypeRep[modeType];
if (!layouts[layoutType]) {
layouts[layoutType] = {};
}
const sections = layoutRep.sections.map((section) => getLayoutSectionModel(objectInfo, record, recordType, section));
if (modeType === 'Edit') {
sections.forEach((section) =>
section.rows.forEach((row) =>
row.items.forEach((item) =>
item.values.forEach((value) =>
editValues[value.field] = {
original: value.value,
current: value.value}
))));
}
layouts[layoutType][modeType] = sections;
}
}
let result = {
layouts,
editValues,
objectInfo,
recordId: record.id,
record
};
return result;
} catch (err) {
console.log('ERROR CREATING LAYOUT MODEL ' + err);
return {layouts: [], editValues: {}, objectInfo:{}, recordId: null, record:null};
}
}
export default {getLayoutItemModel, getLayoutRowModel, getLayoutSectionModel, getLayoutModel, getLayoutModelForDefaults}

Просмотреть файл

@ -0,0 +1,23 @@
import depGraphHelper from '../helpers/depGraphHelper'
const depGraph = (state = {pickListFields: undefined, modalFields: undefined, fieldTree: undefined}, action) => {
switch (action.type) {
case 'EDIT_DEP_GRAPH':
return {
picklistFields : depGraphHelper.getParentToChildPicklistValues(
action.picklists,
action.modalFields,
action.editValues),
modalFields : action.modalFields,
fieldTree : action.fieldTree
}
case 'UPDATE_PICKLIST_FIELDS':
return {
...state,
picklistFields : new Map([...state.picklistFields, ...action.picklistFields])
}
default:
return state
}
}
export default depGraph

Просмотреть файл

@ -2,7 +2,7 @@ const entities = (state = {sobjects: []}, action) => {
switch (action.type) {
case 'RECEIVE_ENTITIES':
return {
sobjects: action.entities.sobjects,
sobjects: action.entities.objects,
receivedAt: action.receivedAt
}
default:

Просмотреть файл

@ -5,10 +5,11 @@ import context from './context'
import header from './header'
import recentitems from './recentitems'
import record from './record'
import picklists from './picklists'
import entities from './entities'
import rawjson from './rawjson'
import error from './error'
import picklists from './picklists.js'
import depGraph from './depGraph.js'
export default combineReducers( {
login,
@ -19,5 +20,6 @@ export default combineReducers( {
picklists,
entities,
rawjson,
error
error,
depGraph
})

Просмотреть файл

@ -1,13 +1,16 @@
const picklists = (state = {}, action) => {
const picklists = (state = {values: undefined}, action) => {
switch (action.type) {
case 'RECEIVE_PICKLIST':
case 'RECEIVE_PICKLISTS':
return {
...state,
[action.url]: action.result
fieldValues : action.result.picklistFieldValues
}
case 'FETCH_PICKLISTS': // clear when new collection is requested
return {
fieldValues: undefined
}
default:
return state
}
}
export default picklists
export default picklists

Просмотреть файл

@ -10,6 +10,8 @@ const rawjson = (state = null, action) => {
return action.defaults;
case 'RECEIVE_CREATE_DEFAULTS':
return action.defaults;
case 'RECEIVE_PICKLISTS':
return action.result;
default:
return null;
}

Просмотреть файл

@ -1,6 +1,6 @@
import recordLayout from '../helpers/recordLayout'
const record = (state = {record: undefined, mode: 'View'}, action) => {
const record = (state = {record: undefined, mode: 'View', prevMode: undefined}, action) => {
switch (action.type) {
case 'RECEIVE_RECORD':
return {
@ -22,6 +22,18 @@ const record = (state = {record: undefined, mode: 'View'}, action) => {
...state,
mode: 'Edit'
}
case 'EDIT_DEP_GRAPH':
return {
...state,
prevMode: action.prevMode, // save previous mode to return to on close
mode: 'EditDepGraph'
}
case 'CLOSE_DEP_GRAPH':
return {
...state,
prevMode: undefined,
mode: action.mode
}
case 'CLEAR_RECORD':
return {
record: undefined

Просмотреть файл

@ -4,7 +4,7 @@ import { receiveCloneDefaults } from '../actions'
export default function* cloneDefaultsFetcher (action) {
let defaultsUrl = action.creds.instanceUrl + '/services/data/v41.0/ui-api/record-defaults/clone/' + action.id + '?formFactor=' + action.context.formFactor;
let defaultsUrl = action.creds.instanceUrl + '/services/data/v42.0/ui-api/record-defaults/clone/' + action.id + '?formFactor=' + action.context.formFactor;
let req = {
method: 'GET',

Просмотреть файл

@ -4,7 +4,7 @@ import { receiveCreateDefaults, showError } from '../actions'
export default function* createDefaultsFetcher (action) {
let defaultsUrl = action.creds.instanceUrl + '/services/data/v41.0/ui-api/record-defaults/create/' + action.apiName + '?formFactor=' + action.context.formFactor;
let defaultsUrl = action.creds.instanceUrl + '/services/data/v42.0/ui-api/record-defaults/create/' + action.apiName + '?formFactor=' + action.context.formFactor;
let req = {
method: 'GET',

Просмотреть файл

@ -0,0 +1,75 @@
import { call, put } from 'redux-saga/effects'
import depGraphHelper from '../helpers/depGraphHelper'
import { updateFieldValue, updatePicklistFields, showError } from '../actions'
export default function* depGraphValueUpdater (action) {
try {
// make a deep copy to not mess with the real state.
const editValues = JSON.parse(JSON.stringify(action.editValues));
const modalFieldsMap = {}
for (var modalField of action.modalFields){
modalFieldsMap[modalField.apiName] = modalField
}
// traverse down to the target field in the tree.
const field = modalFieldsMap[action.field];
var treeNode = action.fieldTree;
for (var i = field.controllingFields.length -1; i>=0; i--) {
var controllingField = field.controllingFields[i]
treeNode = treeNode[controllingField];
}
treeNode = treeNode[action.field];
// now treeNode is the sub-tree rooted at the target field
// do simple update of this field
yield put(updateFieldValue(action.field, action.value));
editValues[action.field].current = action.value
//queue
var queue = []
queue.push(treeNode)
while(queue.length > 0){
var currentTreeNode = queue.shift()
for (const childField of Object.keys(currentTreeNode)) {
const childNode = currentTreeNode[childField];
const currentField = modalFieldsMap[childField];
// update the legal values for this field.
const legalValues = depGraphHelper.getLegalValues(action.picklists, currentField, editValues);
const legalValuesByField = new Map();
legalValuesByField.set(childField,legalValues);
yield put(updatePicklistFields(legalValuesByField));
// make sure the current value is legal.
const currentValue = editValues[currentField.apiName].current;
var found = false;
if (legalValues) { // if no legalValues, checkbox, all values legal.
if (legalValues == []) {
yield put(updateFieldValue(currentField.apiName, null));
editValues[currentField.apiName].current = null
queue.push(childNode)
} else {
legalValues.forEach(legalValue => {
if (legalValue.value == currentValue) {
found = true
}
});
if (!found) {
yield put(updateFieldValue(currentField.apiName, legalValues[0].value));
editValues[currentField.apiName].current = legalValues[0].value
queue.push(childNode)
}
}
}
}
}
} catch(err) {
console.error('Create defaults fetch error: ' + JSON.stringify(err))
}
}

Просмотреть файл

@ -4,12 +4,13 @@ import { receiveEntities } from '../actions'
export default function* entitiesFetcher(action) {
let mruUrl = action.creds.instanceUrl + '/services/data/v41.0/sobjects'
let mruUrl = action.creds.instanceUrl + '/services/data/v42.0/ui-api/object-info'
let req = {
method: 'GET',
headers: {
'Authorization' : 'Bearer ' + action.creds.accessToken}
'Authorization' : 'Bearer ' + action.creds.accessToken,
'X-Chatter-Entity-Encoding': false}
}
try {

Просмотреть файл

@ -1,22 +0,0 @@
import { call, put } from 'redux-saga/effects'
import { receivePicklist } from '../actions'
export default function* picklistFetcher (action) {
let picklistValuesUrl = action.creds.instanceUrl + action.url;
let req = {
method: 'GET',
headers: {
'Authorization' : 'Bearer ' + action.creds.accessToken,
'X-Chatter-Entity-Encoding': false}
};
try {
const response = yield call(fetch, picklistValuesUrl, req)
const responseJson = yield response.json()
yield put(receivePicklist(action.url, responseJson))
} catch(err) {
console.error('Picklist fetch error: ' + err + ' error keys: ' + Object.keys(err));
}
}

Просмотреть файл

@ -0,0 +1,22 @@
import { call, put } from 'redux-saga/effects'
import { receivePicklists } from '../actions'
export default function* picklistsFetcher (action) {
let url = action.creds.instanceUrl + '/services/data/v42.0/ui-api/object-info/' + action.apiName + '/picklist-values/' + action.recordType + '/';
let req = {
method: 'GET',
headers: {
'Authorization' : 'Bearer ' + action.creds.accessToken,
'X-Chatter-Entity-Encoding': false}
};
try {
const response = yield call(fetch, url, req)
const responseJson = yield response.json()
yield put(receivePicklists(url, responseJson))
} catch(err) {
console.error('Error in fetching picklist values collection: ' + JSON.stringify(err))
}
}

Просмотреть файл

@ -4,7 +4,7 @@ import { receiveRecentItems } from '../actions'
export default function* recentItemsFetcher(action) {
let mruUrl = action.creds.instanceUrl + '/services/data/v41.0/recent'
let mruUrl = action.creds.instanceUrl + '/services/data/v42.0/recent'
let req = {
method: 'GET',

Просмотреть файл

@ -4,7 +4,7 @@ import { recordCreateSuccess, showError } from '../actions'
export default function* recordCreator (action) {
let recordDataUrl = action.creds.instanceUrl + '/services/data/v41.0/ui-api/records/';
let recordDataUrl = action.creds.instanceUrl + '/services/data/v42.0/ui-api/records/';
var recordInput = {};
recordInput.apiName = action.apiName;

Просмотреть файл

@ -4,7 +4,7 @@ import { finishedRecordDelete } from '../actions'
export default function* recordDeleter (action) {
let recordUrl = action.creds.instanceUrl + '/services/data/v41.0/ui-api/records/' + action.recordId
let recordUrl = action.creds.instanceUrl + '/services/data/v42.0/ui-api/records/' + action.recordId
let req = {
method: 'DELETE',
headers: {

Просмотреть файл

@ -5,7 +5,9 @@ import { receiveRecord } from '../actions'
export default function* recordFetcher (action) {
let recordViewUrl = action.creds.instanceUrl + '/services/data/v41.0/ui-api/record-ui/' + action.recordId + '?formFactor=' + action.context.formFactor + '&modes=View,Edit';
// TODO: Add LayoutType to the state so that we can change view based on them
let recordViewUrl = action.creds.instanceUrl + '/services/data/v42.0/ui-api/record-ui/' + action.recordId + '?formFactor=' + action.context.formFactor + '&modes=View,Edit';
let req = {
method: 'GET',
headers: {

Просмотреть файл

@ -4,7 +4,7 @@ import { recordUpdateSuccess, showError } from '../actions'
export default function* recordUpdater (action) {
let recordDataUrl = action.creds.instanceUrl + '/services/data/v41.0/ui-api/records/' + action.recordId;
let recordDataUrl = action.creds.instanceUrl + '/services/data/v42.0/ui-api/records/' + action.recordId;
var recordInput = {};
recordInput.fields = {};

Просмотреть файл

@ -4,20 +4,22 @@ import recentItemsFetcher from './recentItemsFetcher'
import recordFetcher from './recordFetcher'
import recordDeleter from './recordDeleter'
import recordUpdater from './recordUpdater'
import picklistFetcher from './picklistFetcher'
import entitiesFetcher from './entitiesFetcher'
import recordCreator from './recordCreator'
import cloneDefaultsFetcher from './cloneDefaultsFetcher'
import createDefaultsFetcher from './createDefaultsFetcher'
import picklistsFetcher from './picklistsFetcher'
import depGraphValueUpdater from './depGraphValueUpdater'
export default function* rootSaga() {
yield takeEvery('FETCH_RECORD', recordFetcher)
yield takeEvery('FETCH_RECENT_ITEMS', recentItemsFetcher)
yield takeEvery('DELETE_RECORD', recordDeleter)
yield takeEvery('SAVE_RECORD', recordUpdater)
yield takeEvery('FETCH_PICKLIST', picklistFetcher)
yield takeEvery('FETCH_ENTITIES', entitiesFetcher)
yield takeEvery('CREATE_RECORD', recordCreator)
yield takeEvery('FETCH_CREATE_DEFAULTS', createDefaultsFetcher);
yield takeEvery('FETCH_CLONE_DEFAULTS', cloneDefaultsFetcher);
yield takeEvery('FETCH_PICKLISTS', picklistsFetcher);
yield takeEvery('UPDATE_DEP_GRAPH_FIELD_VALUE', depGraphValueUpdater)
}

Просмотреть файл

@ -1,7 +1,7 @@
web:
build: .
command: 'bash -c ''node app.js'''
working_dir: /app/user
working_dir: /usr/src/app
environment:
PORT: 8050
NGINX_PORT: 8443

99
lib/json-inspector.css Normal file
Просмотреть файл

@ -0,0 +1,99 @@
.json-inspector,
.json-inspector__selection {
font: 14px/1.4 Consolas, monospace;
}
.json-inspector__leaf {
padding-left: 10px;
}
.json-inspector__line {
display: block;
position: relative;
cursor: default;
}
.json-inspector__line:after {
content: '';
position: absolute;
top: 0;
left: -200px;
right: -50px;
bottom: 0;
z-index: -1;
pointer-events: none;
}
.json-inspector__line:hover:after {
background: rgba(0, 0, 0, 0.06);
}
.json-inspector__leaf_composite > .json-inspector__line {
cursor: pointer;
}
.json-inspector__radio,
.json-inspector__flatpath {
display: none;
}
.json-inspector__value {
margin-left: 5px;
}
.json-inspector__search {
min-width: 300px;
margin: 0 10px 10px 0;
padding: 2px;
}
.json-inspector__key {
color: #505050;
}
.json-inspector__value_helper,
.json-inspector__value_null,
.json-inspector__not-found {
color: #b0b0b0;
}
.json-inspector__value_string {
color: #798953;
}
.json-inspector__value_boolean {
color: #75b5aa;
}
.json-inspector__value_number {
color: #d28445;
}
.json-inspector__hl {
background: #ff0;
box-shadow: 0 -1px 0 2px #ff0;
border-radius: 2px;
}
.json-inspector__show-original {
display: inline-block;
padding: 0 6px;
color: #666;
cursor: pointer;
}
.json-inspector__show-original:hover {
color: #111;
}
.json-inspector__show-original:before {
content: '⥂';
}
.json-inspector__show-original:hover:after {
content: ' expand'
}

Просмотреть файл

@ -9,23 +9,26 @@
"url": "https://github.com/forcedotcom/RecordViewer.git"
},
"dependencies": {
"@salesforce-ux/design-system": "2.1.2",
"body-parser": "1.13.3",
"cookie-parser": "1.3.5",
"express": "4.13.3",
"express-session": "1.11.3",
"jade": "1.11.0",
"morgan": "1.6.1",
"os-homedir": "1.0.1",
"@salesforce-ux/design-system": "^2.1.2",
"body-parser": "^1.13.3",
"cookie-parser": "^1.3.5",
"express": "^4.13.3",
"express-session": "^1.11.3",
"morgan": "^1.6.1",
"os-homedir": "^1.0.1",
"pug": "^2.0.1",
"q": "0.8.12",
"react": "^15.4.0",
"react-bootstrap": "0.13.3",
"react-dom": "^15.4.0",
"react-json-inspector": "7.1.0",
"react-json-pretty": "^1.2.1",
"react-modal-dialog": "4.0.7",
"react-redux": "^4.4.6",
"redux": "^3.6.0",
"redux-saga": "^0.13.0",
"serve-static": "1.10.0",
"stylus": "0.52.0",
"serve-static": "^1.10.0",
"stylus": "^0.54.3",
"xml2js": "0.4.9"
},
"devDependencies": {

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

@ -8,6 +8,7 @@ html
link(rel='stylesheet', href='/libs/salesforce-ux/design-system/styles/salesforce-lightning-design-system.min.css')
link(rel='stylesheet', href='//maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css')
link(rel='stylesheet', href='json-inspector.css')
script(type='text/javascript', src='https://code.jquery.com/jquery-2.0.3.min.js')
script(type='text/javascript', src='root.js')