ipp_quickbase_devkit 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +87 -0
- data/README.rdoc +112 -0
- data/doc/QuickBaseClient.rb.htm +1896 -0
- data/doc/ReleaseNotes.txt +43 -0
- data/doc/qbc.makeCSVFile.qbc +4 -0
- data/doc/qbc.makeCSVFile.rb +4 -0
- data/doc/quickbase_adapter.rb.htm +399 -0
- data/examples/cookbookfiles/QuickBaseAPICookbook.html +2590 -0
- data/examples/cookbookfiles/addChangeRemoveUserRole.rb +21 -0
- data/examples/cookbookfiles/addOrEditRecord.rb +10 -0
- data/examples/cookbookfiles/application_object.rb +55 -0
- data/examples/cookbookfiles/applyRubyFormulas.rb +10 -0
- data/examples/cookbookfiles/average.rb +27 -0
- data/examples/cookbookfiles/backupApplication.rb +8 -0
- data/examples/cookbookfiles/cacheSchemas.rb +53 -0
- data/examples/cookbookfiles/calculateRunningTotals.rb +11 -0
- data/examples/cookbookfiles/copyrecords.rb +73 -0
- data/examples/cookbookfiles/count.rb +26 -0
- data/examples/cookbookfiles/createRecordNavigatorHTML.rb +42 -0
- data/examples/cookbookfiles/createReportDashboard.rb +35 -0
- data/examples/cookbookfiles/createTable.rb +12 -0
- data/examples/cookbookfiles/deviation.rb +25 -0
- data/examples/cookbookfiles/downloadCookbook.rb +97 -0
- data/examples/cookbookfiles/downloadFile.rb +10 -0
- data/examples/cookbookfiles/downloadFilesToFolder.rb +81 -0
- data/examples/cookbookfiles/downloadToTextFile.rb +64 -0
- data/examples/cookbookfiles/dumpSchema.rb +11 -0
- data/examples/cookbookfiles/duplicateRecord.rb +8 -0
- data/examples/cookbookfiles/dynamicMethods.rb +33 -0
- data/examples/cookbookfiles/editRecords.rb +15 -0
- data/examples/cookbookfiles/findJohnsLast10Records.rb +17 -0
- data/examples/cookbookfiles/findRubyRecords.rb +17 -0
- data/examples/cookbookfiles/formatCurrency.rb +24 -0
- data/examples/cookbookfiles/formatDate.rb +10 -0
- data/examples/cookbookfiles/formatDuration.rb +27 -0
- data/examples/cookbookfiles/formatPercent.rb +24 -0
- data/examples/cookbookfiles/getAllValuesForFields.rb +18 -0
- data/examples/cookbookfiles/getAppDTMInfo.rb +29 -0
- data/examples/cookbookfiles/getApplicationVariable.rb +5 -0
- data/examples/cookbookfiles/getChildTableDBID.rb +11 -0
- data/examples/cookbookfiles/getColumnListForReport.rb +6 -0
- data/examples/cookbookfiles/getFieldChoices.rb +13 -0
- data/examples/cookbookfiles/getFieldIDs.rb +6 -0
- data/examples/cookbookfiles/getFieldNames.rb +6 -0
- data/examples/cookbookfiles/getLastModTime.rb +8 -0
- data/examples/cookbookfiles/getLastRecModTime.rb +8 -0
- data/examples/cookbookfiles/getNumRecords.rb +8 -0
- data/examples/cookbookfiles/getNumTables.rb +4 -0
- data/examples/cookbookfiles/getRecord.rb +5 -0
- data/examples/cookbookfiles/getRecordDisplayURL.rb +13 -0
- data/examples/cookbookfiles/getRecordsAddedToday.rb +20 -0
- data/examples/cookbookfiles/getRecordsAsJSON.rb +6 -0
- data/examples/cookbookfiles/getReportNames.rb +25 -0
- data/examples/cookbookfiles/getRoleInfo.rb +48 -0
- data/examples/cookbookfiles/getServerStatus.rb +11 -0
- data/examples/cookbookfiles/getSortListForReport.rb +6 -0
- data/examples/cookbookfiles/getTableIDs.rb +6 -0
- data/examples/cookbookfiles/getTableName.rb +8 -0
- data/examples/cookbookfiles/getTableNames.rb +25 -0
- data/examples/cookbookfiles/getTimeCreated.rb +8 -0
- data/examples/cookbookfiles/getTimeInMilliseconds.rb +5 -0
- data/examples/cookbookfiles/getUserInfo.rb +26 -0
- data/examples/cookbookfiles/getUserRole.rb +15 -0
- data/examples/cookbookfiles/intranet.rb +101 -0
- data/examples/cookbookfiles/isAverageField.rb +17 -0
- data/examples/cookbookfiles/isDbidString.rb +8 -0
- data/examples/cookbookfiles/isTotalField.rb +16 -0
- data/examples/cookbookfiles/iterateDBPages.rb +8 -0
- data/examples/cookbookfiles/iterateFilteredRecords.rb +12 -0
- data/examples/cookbookfiles/iterateJoinRecords.rb +68 -0
- data/examples/cookbookfiles/iterateRecordInfos.rb +8 -0
- data/examples/cookbookfiles/iterateRecords.rb +23 -0
- data/examples/cookbookfiles/iterateSummaryRecords.rb +13 -0
- data/examples/cookbookfiles/iterateUnionRecords.rb +38 -0
- data/examples/cookbookfiles/listAccessibleApplications.rb +6 -0
- data/examples/cookbookfiles/logRequestAndResponseXML.rb +8 -0
- data/examples/cookbookfiles/lookupFieldPropertyByName.rb +62 -0
- data/examples/cookbookfiles/lookupFieldTypeByName.rb +10 -0
- data/examples/cookbookfiles/makeCSVFile.rb +4 -0
- data/examples/cookbookfiles/makeSlideShow.rb +42 -0
- data/examples/cookbookfiles/makerecs.rb +64 -0
- data/examples/cookbookfiles/max.rb +26 -0
- data/examples/cookbookfiles/min.rb +26 -0
- data/examples/cookbookfiles/percent.rb +29 -0
- data/examples/cookbookfiles/printChildElements.rb +54 -0
- data/examples/cookbookfiles/printNewRecords.rb +12 -0
- data/examples/cookbookfiles/processRESTRequest.rb +21 -0
- data/examples/cookbookfiles/provisionAndInviteNewUser.rb +13 -0
- data/examples/cookbookfiles/purgeRecords.rb +15 -0
- data/examples/cookbookfiles/quickbase_adapter.rb.htm +397 -0
- data/examples/cookbookfiles/quickbase_record_finder.zip +0 -0
- data/examples/cookbookfiles/recordAndFieldIterator.rb +24 -0
- data/examples/cookbookfiles/runImport.rb +9 -0
- data/examples/cookbookfiles/runQuickBaseTwitterConnector.rb +41 -0
- data/examples/cookbookfiles/sendToQuickBase.rb +33 -0
- data/examples/cookbookfiles/setDBvar.rb +6 -0
- data/examples/cookbookfiles/showRequestAndResponseXML.rb +8 -0
- data/examples/cookbookfiles/sqlQuery.rb +11 -0
- data/examples/cookbookfiles/stopOnError.rb +10 -0
- data/examples/cookbookfiles/sum.rb +26 -0
- data/examples/cookbookfiles/twitterFromQuickBase.rb +42 -0
- data/examples/cookbookfiles/twitterWithQuickBase.rb +36 -0
- data/examples/cookbookfiles/uploadCSVData.rb +20 -0
- data/examples/cookbookfiles/uploadExcelData.rb +22 -0
- data/examples/cookbookfiles/uploadFileEveryHour.rb +18 -0
- data/examples/cookbookfiles/uploadFileIntoNewRecord.rb +8 -0
- data/examples/cookbookfiles/uploadFilesFromFolder.exe +0 -0
- data/examples/cookbookfiles/uploadFilesFromFolder.rb +69 -0
- data/examples/cookbookfiles/useCompanyURL.rb +12 -0
- data/examples/cookbookfiles/userRoles.rb +49 -0
- data/examples/cookbookfiles/watchCommunityForum.rb +5 -0
- data/examples/cookbookfiles/wikifyTable.rb +29 -0
- data/examples/cookbookfiles/xmlShortcuts.rb +33 -0
- data/examples/pmp/app/controllers/application.rb +7 -0
- data/examples/pmp/app/controllers/contacts_controller.rb +8 -0
- data/examples/pmp/app/controllers/document_library_controller.rb +2 -0
- data/examples/pmp/app/controllers/issues_controller.rb +5 -0
- data/examples/pmp/app/controllers/projects_controller.rb +22 -0
- data/examples/pmp/app/controllers/resources_controller.rb +2 -0
- data/examples/pmp/app/controllers/tasks_controller.rb +13 -0
- data/examples/pmp/app/controllers/time_cards_controller.rb +5 -0
- data/examples/pmp/app/helpers/application_helper.rb +3 -0
- data/examples/pmp/app/helpers/contacts_helper.rb +2 -0
- data/examples/pmp/app/helpers/document_library_helper.rb +2 -0
- data/examples/pmp/app/helpers/issues_helper.rb +2 -0
- data/examples/pmp/app/helpers/projects_helper.rb +2 -0
- data/examples/pmp/app/helpers/resources_helper.rb +2 -0
- data/examples/pmp/app/helpers/tasks_helper.rb +2 -0
- data/examples/pmp/app/helpers/time_cards_helper.rb +2 -0
- data/examples/pmp/app/models/contacts.rb +26 -0
- data/examples/pmp/app/models/document_library.rb +2 -0
- data/examples/pmp/app/models/issues.rb +6 -0
- data/examples/pmp/app/models/projects.rb +26 -0
- data/examples/pmp/app/models/resources.rb +2 -0
- data/examples/pmp/app/models/tasks.rb +12 -0
- data/examples/pmp/app/models/time_cards.rb +7 -0
- data/examples/pmp/app/schemas/contacts.xml +1 -0
- data/examples/pmp/app/schemas/document_library.xml +1 -0
- data/examples/pmp/app/schemas/issues.xml +1 -0
- data/examples/pmp/app/schemas/pmp.xml +1 -0
- data/examples/pmp/app/schemas/projects.xml +1 -0
- data/examples/pmp/app/schemas/readme.txt +8 -0
- data/examples/pmp/app/schemas/resources.xml +1 -0
- data/examples/pmp/app/schemas/tasks.xml +1 -0
- data/examples/pmp/app/schemas/time_cards.xml +1 -0
- data/examples/pmp/app/views/contacts/companies.rhtml +31 -0
- data/examples/pmp/app/views/contacts/project_contacts.rhtml +31 -0
- data/examples/pmp/app/views/issues/filter_issues.rhtml +26 -0
- data/examples/pmp/app/views/layouts/application.rhtml +56 -0
- data/examples/pmp/app/views/projects/all_projects.rhtml +33 -0
- data/examples/pmp/app/views/projects/home.rhtml +11 -0
- data/examples/pmp/app/views/projects/my_open_projects.rhtml +27 -0
- data/examples/pmp/app/views/projects/open_projects.rhtml +44 -0
- data/examples/pmp/app/views/projects/project_sorted_by_company.rhtml +40 -0
- data/examples/pmp/app/views/projects/projects_sorted_by_priority.rhtml +30 -0
- data/examples/pmp/app/views/projects/updated_projects.rhtml +0 -0
- data/examples/pmp/app/views/tasks/all_tasks.rhtml +27 -0
- data/examples/pmp/app/views/tasks/search.rhtml +23 -0
- data/examples/pmp/app/views/tasks/search2.rhtml +23 -0
- data/examples/pmp/app/views/tasks/search3.rhtml +23 -0
- data/examples/pmp/app/views/time_cards/summary.rhtml +38 -0
- data/examples/pmp/config/boot.rb +45 -0
- data/examples/pmp/config/database.yml +30 -0
- data/examples/pmp/config/environment.rb +60 -0
- data/examples/pmp/config/environments/development.rb +21 -0
- data/examples/pmp/config/environments/production.rb +18 -0
- data/examples/pmp/config/environments/test.rb +19 -0
- data/examples/pmp/config/routes.rb +23 -0
- data/examples/pmp/db/migrate/001_create_projects.rb +10 -0
- data/examples/pmp/db/migrate/002_create_tasks.rb +10 -0
- data/examples/pmp/db/migrate/003_create_issues.rb +10 -0
- data/examples/pmp/db/migrate/004_create_document_libraries.rb +10 -0
- data/examples/pmp/db/migrate/005_create_resources.rb +10 -0
- data/examples/pmp/db/migrate/006_create_time_cards.rb +10 -0
- data/examples/pmp/db/migrate/007_create_contacts.rb +10 -0
- data/examples/pmp/public/404.html +30 -0
- data/examples/pmp/public/500.html +30 -0
- data/examples/pmp/public/app.index.html +277 -0
- data/examples/pmp/public/dispatch.cgi +10 -0
- data/examples/pmp/public/dispatch.fcgi +24 -0
- data/examples/pmp/public/dispatch.rb +10 -0
- data/examples/pmp/public/favicon.ico +0 -0
- data/examples/pmp/public/images/rails.png +0 -0
- data/examples/pmp/public/javascripts/application.js +2 -0
- data/examples/pmp/public/javascripts/controls.js +833 -0
- data/examples/pmp/public/javascripts/dragdrop.js +942 -0
- data/examples/pmp/public/javascripts/effects.js +1088 -0
- data/examples/pmp/public/javascripts/prototype.js +2515 -0
- data/examples/pmp/public/robots.txt +1 -0
- data/examples/pmp/test/fixtures/contacts.yml +5 -0
- data/examples/pmp/test/fixtures/document_libraries.yml +5 -0
- data/examples/pmp/test/fixtures/issues.yml +5 -0
- data/examples/pmp/test/fixtures/projects.yml +5 -0
- data/examples/pmp/test/fixtures/resources.yml +5 -0
- data/examples/pmp/test/fixtures/tasks.yml +5 -0
- data/examples/pmp/test/fixtures/time_cards.yml +5 -0
- data/examples/pmp/test/functional/contacts_controller_test.rb +18 -0
- data/examples/pmp/test/functional/document_library_controller_test.rb +18 -0
- data/examples/pmp/test/functional/issues_controller_test.rb +18 -0
- data/examples/pmp/test/functional/projects_controller_test.rb +18 -0
- data/examples/pmp/test/functional/resources_controller_test.rb +18 -0
- data/examples/pmp/test/functional/tasks_controller_test.rb +18 -0
- data/examples/pmp/test/functional/time_cards_controller_test.rb +18 -0
- data/examples/pmp/test/test_helper.rb +28 -0
- data/examples/pmp/test/unit/contacts_test.rb +10 -0
- data/examples/pmp/test/unit/document_library_test.rb +10 -0
- data/examples/pmp/test/unit/issues_test.rb +10 -0
- data/examples/pmp/test/unit/projects_test.rb +10 -0
- data/examples/pmp/test/unit/resources_test.rb +10 -0
- data/examples/pmp/test/unit/tasks_test.rb +10 -0
- data/examples/pmp/test/unit/time_cards_test.rb +10 -0
- data/lib/QuickBaseClient.rb +5054 -0
- data/lib/QuickBaseCommandLineClient.rb +401 -0
- data/lib/QuickBaseContactsAppBuilder.rb +419 -0
- data/lib/QuickBaseEmailer.rb +334 -0
- data/lib/QuickBaseEventNotifier.rb +592 -0
- data/lib/QuickBaseMisc.rb +96 -0
- data/lib/QuickBaseObjects.rb +566 -0
- data/lib/QuickBaseRSSGenerator.rb +286 -0
- data/lib/QuickBaseTextData.rb +545 -0
- data/lib/QuickBaseTwitterConnector.rb +300 -0
- data/lib/QuickBaseWebClient.rb +126 -0
- data/lib/WorkPlaceClient.rb +45 -0
- data/lib/qbc.makeCSVFile.qbc +4 -0
- data/lib/qbc.makeCSVFile.rb +17 -0
- data/lib/quickbase_adapter.rb +320 -0
- data/lib/runFieldEntryDialog.rb +151 -0
- data/lib/runOfflineFieldEntryDialog.rb +203 -0
- data/rakefile +100 -0
- data/test/run_tests.bat +7 -0
- data/test/spec_all_tests.rb +13 -0
- data/test/spec_smoke_tests.rb +58 -0
- data/test/spec_workplace_addrecord_test.rb +46 -0
- data/test/spec_workplace_base_test.rb +57 -0
- data/test/spec_workplace_editrecord_test.rb +38 -0
- data/test/spec_workplace_json_test.rb +38 -0
- data/test/spec_workplace_objects_test.rb +39 -0
- data/test/spec_workplace_smoke_tests.rb +45 -0
- metadata +353 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
#--#####################################################################
|
|
2
|
+
# Copyright (c) 2009 Gareth Lewis and Intuit, Inc.
|
|
3
|
+
#
|
|
4
|
+
# All rights reserved. This program and the accompanying materials
|
|
5
|
+
# are made available under the terms of the Eclipse Public License v1.0
|
|
6
|
+
# which accompanies this distribution, and is available at
|
|
7
|
+
# http://www.opensource.org/licenses/eclipse-1.0.php
|
|
8
|
+
#
|
|
9
|
+
# Contributors:
|
|
10
|
+
# Gareth Lewis - Initial contribution.
|
|
11
|
+
# Intuit Partner Platform.
|
|
12
|
+
#++#####################################################################
|
|
13
|
+
|
|
14
|
+
require 'QuickBaseClient'
|
|
15
|
+
|
|
16
|
+
module QuickBase
|
|
17
|
+
|
|
18
|
+
# This class generates RSS data from specific fields in one or more
|
|
19
|
+
# QuickBase tables. It is intended to make standard RSS text that can
|
|
20
|
+
# be displayed by RSS readers or processed by other utilities.
|
|
21
|
+
#
|
|
22
|
+
# See the test() method at the bottom of this file for an example
|
|
23
|
+
# of using this class.
|
|
24
|
+
#
|
|
25
|
+
# Note that by default this class sorts the RSS items by <pubDate>
|
|
26
|
+
# descending order (i.e. most recent first); this means
|
|
27
|
+
# that items generated from records from different tables can be
|
|
28
|
+
# interspersed which each other. Some RSS readers do not appear
|
|
29
|
+
# to interpret <pubDate> correctly and therefore don't sort items
|
|
30
|
+
# correctly by themselves. Use 'unSorted' to prevent this class
|
|
31
|
+
# from sorting <items>. Also note that the sort order of records
|
|
32
|
+
# retrieved from tables can be set separately for each table, which
|
|
33
|
+
# is useful if the output is not going to be processed by an RSS readser.
|
|
34
|
+
class RSSGenerator
|
|
35
|
+
|
|
36
|
+
# Nested class to encapsulate RSS retrieval options for a QuickBase table.
|
|
37
|
+
class Table
|
|
38
|
+
|
|
39
|
+
attr_reader :dbid, :tableName, :fields, :clist, :slist, :query, :qid, :qname, :numRecords, :options
|
|
40
|
+
attr_reader :optionalFields, :title, :link, :description
|
|
41
|
+
|
|
42
|
+
# 'tableName' is the table name as it should appear in the output.
|
|
43
|
+
# Gets all records by default, sorted by Date Modified ("2") in descending order.
|
|
44
|
+
# Only retrieves fields in 'fields' parameter.
|
|
45
|
+
# <pubDate> is set to Date Modified ("2") by default
|
|
46
|
+
# <link> is set to Record ID# ("3") by default
|
|
47
|
+
# any additional quickbase field can be included in 'fields'
|
|
48
|
+
#-----------------------------------------------------------------------------
|
|
49
|
+
def initialize( dbid, tableName, fields, query = nil, qid = nil, qname = nil, numRecords = 0, slist = "2", options = "sortorder-D" )
|
|
50
|
+
|
|
51
|
+
raise "fields parameter must be a Hash" if !fields.is_a?( Hash )
|
|
52
|
+
|
|
53
|
+
@dbid, @tableName, @fields = dbid, tableName, fields
|
|
54
|
+
@query, @qid, @qname, @numRecords, @slist, @options = query, qid, qname, numRecords, slist, options
|
|
55
|
+
|
|
56
|
+
@fields["pubDate"] = "2" if @fields["pubDate"].nil?
|
|
57
|
+
@fields["link"] = "3" if @fields["link"].nil?
|
|
58
|
+
|
|
59
|
+
@optionalFields = Hash.new
|
|
60
|
+
|
|
61
|
+
@clist = ""
|
|
62
|
+
if @fields["description"] and @fields["title"]
|
|
63
|
+
@fields.each{ |k,v|
|
|
64
|
+
@clist << "#{v}."
|
|
65
|
+
if k != "pubDate" and k != "link" and k != "description" and k != "title"
|
|
66
|
+
@optionalFields[v] = k
|
|
67
|
+
end
|
|
68
|
+
}
|
|
69
|
+
@clist[-1] = ""
|
|
70
|
+
else
|
|
71
|
+
raise "fields parameter must include a 'description' and 'title'"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
@title = "Default RSS Item Title"
|
|
75
|
+
@link = "http://www.quickbase.com/db/main"
|
|
76
|
+
@description = "Default RSS item description"
|
|
77
|
+
|
|
78
|
+
@options << ".num-#{numRecords}" if numRecords > 0
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
end # class Table
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def initialize( qbc )
|
|
85
|
+
raise "Bad qbc parameter. qbc must be an instance of QuickBase::Client." if !qbc.is_a?( Client )
|
|
86
|
+
@qbc = qbc
|
|
87
|
+
raise "Please sign into QuickBase before using the RSSGenerator class" if @qbc.ticket.nil?
|
|
88
|
+
@items = Hash.new
|
|
89
|
+
@sortableItems = Hash.new
|
|
90
|
+
@sorted = true
|
|
91
|
+
@tables = Array.new
|
|
92
|
+
@timeNow = @namespace = ""
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Generates RSS <item>s from a filtered list of records and fields from a table.
|
|
96
|
+
def generateRSSItems( table )
|
|
97
|
+
|
|
98
|
+
@qbc.doQuery( table.dbid, table.query, table.qid, table.qname, table.clist, table.slist, "structured", table.options )
|
|
99
|
+
|
|
100
|
+
@qbc.eachRecord { |r|
|
|
101
|
+
|
|
102
|
+
pubDate = ""
|
|
103
|
+
title = ""
|
|
104
|
+
description = ""
|
|
105
|
+
link = ""
|
|
106
|
+
otherFieldValues = ""
|
|
107
|
+
itemText = ""
|
|
108
|
+
sortValue = 0
|
|
109
|
+
|
|
110
|
+
@qbc.eachField(r){ |f|
|
|
111
|
+
case f.attributes[ "id" ]
|
|
112
|
+
when table.fields[ "title" ]
|
|
113
|
+
title = f.text if f.has_text?
|
|
114
|
+
next
|
|
115
|
+
when table.fields[ "description" ]
|
|
116
|
+
description = f.text if f.has_text?
|
|
117
|
+
next
|
|
118
|
+
when table.fields[ "pubDate" ]
|
|
119
|
+
pubDate = f.text if f.has_text?
|
|
120
|
+
next
|
|
121
|
+
when table.fields[ "link" ]
|
|
122
|
+
link = f.text if f.has_text?
|
|
123
|
+
next
|
|
124
|
+
when table.slist
|
|
125
|
+
sortValue = f.text if f.has_text? and @sorted
|
|
126
|
+
next
|
|
127
|
+
end
|
|
128
|
+
table.optionalFields.each{ |fid,fieldName|
|
|
129
|
+
if f.attributes["id"] == fid and f.has_text?
|
|
130
|
+
field, value = onSetItemField( "#{@namespace}#{fieldName}", f.text )
|
|
131
|
+
otherFieldValues << " <#{field}>#{@qbc.encodeXML(value)}</#{field}>\n"
|
|
132
|
+
end
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
titleHash = "#{table.tableName}(#{link}): #{description[0..20]}..."
|
|
137
|
+
link = "https://www.quickbase.com/db/#{table.dbid}?a=dr&rid=#{link}"
|
|
138
|
+
|
|
139
|
+
itemText << " <item>\n"
|
|
140
|
+
|
|
141
|
+
title << " (#{table.tableName})"
|
|
142
|
+
field, value = onSetItemField( "#{@namespace}title", title )
|
|
143
|
+
itemText << " <#{field}>#{@qbc.encodeXML(value)}</#{field}>\n"
|
|
144
|
+
|
|
145
|
+
field, value = onSetItemField( "#{@namespace}pubDate", @qbc.formatDate(pubDate, "%a, %d %b %Y %H:%M PST" ) )
|
|
146
|
+
itemText << " <#{field}>#{value}</#{field}>\n"
|
|
147
|
+
|
|
148
|
+
guid = link.dup
|
|
149
|
+
field, value = onSetItemField( "#{@namespace}link", link )
|
|
150
|
+
itemText << " <#{field}>#{@qbc.encodeXML(value)}</#{field}>\n"
|
|
151
|
+
|
|
152
|
+
field, value = onSetItemField( "#{@namespace}guid", guid )
|
|
153
|
+
itemText << " <#{field}>#{@qbc.encodeXML(value)}</#{field}>\n"
|
|
154
|
+
|
|
155
|
+
field, value = onSetItemField( "#{@namespace}description", description )
|
|
156
|
+
itemText << " <#{field}>#{@qbc.encodeXML(value)}</#{field}>\n"
|
|
157
|
+
|
|
158
|
+
itemText << otherFieldValues
|
|
159
|
+
itemText << " </item>\n"
|
|
160
|
+
|
|
161
|
+
@items[titleHash] = itemText
|
|
162
|
+
@sortableItems[titleHash] = sortValue
|
|
163
|
+
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# To modify item field names or values before they are inserted into the RSS output,
|
|
169
|
+
# derive from this class, override this method, and modify the return values.
|
|
170
|
+
def onSetItemField( field, value )
|
|
171
|
+
return field, value
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Prepend 'quickbase:' namespace to all data from QuickBase.
|
|
175
|
+
# intended for use by RSS processors other than RSS Readers.
|
|
176
|
+
def useNamespace
|
|
177
|
+
@namespace = "quickbase:"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Items are sorted by <pubDate> unless this method is called.
|
|
181
|
+
def unSorted
|
|
182
|
+
@sorted = false
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Set the title for the RSS feed generated by this class.
|
|
186
|
+
def setTitle( title )
|
|
187
|
+
@title = " <#{@namespace}title>#{@qbc.encodeXML(title)}</#{@namespace}title>\n"
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Set the link for the RSS feed generated by this class.
|
|
191
|
+
# Set isDBID = false if this is not a QuickBase dbid.
|
|
192
|
+
def setLink( link, isDBID = true )
|
|
193
|
+
if isDBID
|
|
194
|
+
@link = " <#{@namespace}link>https://www.quickbase.com/db/#{link}</#{@namespace}link>\n"
|
|
195
|
+
else
|
|
196
|
+
@link = " <#{@namespace}link>#{@qbc.encodeXML(link)}</#{@namespace}link>\n"
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Insert the current time at the end of the descripton for the RSS
|
|
201
|
+
# feed generated by this class.
|
|
202
|
+
def appendTimeToDescription
|
|
203
|
+
@timeNow = " (#{Time.now})"
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Set the description for the RSS feed generated by this class.
|
|
207
|
+
def setDescription( description, appendTime = true )
|
|
208
|
+
appendTimeToDescription if appendTime
|
|
209
|
+
@description = " <#{@namespace}description>#{@qbc.encodeXML(description)}#{@timeNow}</#{@namespace}description>\n"
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def header
|
|
213
|
+
text = "<?xml version=\"1.0\" ?>\n"
|
|
214
|
+
text << " <rss version=\"2.0\">\n"
|
|
215
|
+
text << " <channel>\n"
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def footer
|
|
219
|
+
text = " </channel>\n"
|
|
220
|
+
text << " </rss>\n"
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Add a QuickBase table to the list of tables from which to generate RSS
|
|
224
|
+
#
|
|
225
|
+
# * dbid = QuickBase table dbid
|
|
226
|
+
# * tableName = the name of the QuickBase table as it should appear in the generated RSS
|
|
227
|
+
# * fields = Hash of RSS fields and the IDs of QuickBase fields , e.g. { "description" => "10", "myRSSfield" => "13" }
|
|
228
|
+
# * query, qid, qname = query parameters passed directly to QuickBase::Client,doQuery()
|
|
229
|
+
# * numItems = the number of records to retrieve from QuickBase
|
|
230
|
+
def addTable( dbid, tableName, fields, query = nil, qid = nil, qname = nil, numRecords = 0 )
|
|
231
|
+
t = Table.new( dbid, tableName, fields, query, qid, qname, numRecords )
|
|
232
|
+
@tables << t
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Generate all the RSS text for all the tables.
|
|
236
|
+
def generateRSStext
|
|
237
|
+
rssText = ""
|
|
238
|
+
if @tables.length > 0
|
|
239
|
+
rssText = header
|
|
240
|
+
rssText << @title if @title
|
|
241
|
+
rssText << @link if @link
|
|
242
|
+
rssText << @description if @description
|
|
243
|
+
@tables.each{ |table| generateRSSItems( table ) }
|
|
244
|
+
if @sorted
|
|
245
|
+
sortedItems = @sortableItems.sort{|a,b| b[1]<=>a[1] if a[1] and b[1] }
|
|
246
|
+
sortedItems.each{ |item| rssText << @items[item[0]] }
|
|
247
|
+
else
|
|
248
|
+
@items.each{ |item| rssText << @items[item[0]] }
|
|
249
|
+
end
|
|
250
|
+
rssText << footer
|
|
251
|
+
else
|
|
252
|
+
raise "Call addTable() one or more times before calling generateRSS"
|
|
253
|
+
end
|
|
254
|
+
rssText
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
end # class RSSGenerator
|
|
258
|
+
|
|
259
|
+
end #module QuickBase
|
|
260
|
+
|
|
261
|
+
# The following test code generates RSS text from recently modified
|
|
262
|
+
# records in the QuickBase Community Forum and KnowledgeBase.
|
|
263
|
+
def test( username, password )
|
|
264
|
+
|
|
265
|
+
qbc = QuickBase::Client.new( username, password )
|
|
266
|
+
qbRSSgen = QuickBase::RSSGenerator.new( qbc )
|
|
267
|
+
|
|
268
|
+
qbRSSgen.unSorted
|
|
269
|
+
|
|
270
|
+
qbRSSgen.setTitle( "QuickBase Forum/KnowledgeBase RSS" )
|
|
271
|
+
qbRSSgen.setLink( "main" )
|
|
272
|
+
qbRSSgen.setDescription( "RSS view of QuickBase Community Forum and KnowledgeBase" )
|
|
273
|
+
|
|
274
|
+
qbRSSgen.addTable("8emtadvk", "Community Forum", { "title" => "6", "description" => "10" }, nil, nil, nil, 75 )
|
|
275
|
+
qbRSSgen.addTable( "6mztyxu8", "KnowledgeBase", { "title" => "5", "description" => "6" }, nil, nil, nil, 50 )
|
|
276
|
+
|
|
277
|
+
rssText = qbRSSgen.generateRSStext
|
|
278
|
+
|
|
279
|
+
File.open( "QuickBaseInfoRSS.xml", "w" ) { |file| file.write( rssText ) }
|
|
280
|
+
print "\nPlease view QuickBaseInfoRSS.xml in an RSS reader, a browser or an XML editor.\n"
|
|
281
|
+
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# uncomment the following line to test this class using 'ruby QuickBaseRSSGenerator.rb username password'
|
|
285
|
+
#test( ARGV[0], ARGV[1] ) if ARGV[1]
|
|
286
|
+
|
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
#--#####################################################################
|
|
2
|
+
# Copyright (c) 2009 Gareth Lewis and Intuit, Inc.
|
|
3
|
+
#
|
|
4
|
+
# All rights reserved. This program and the accompanying materials
|
|
5
|
+
# are made available under the terms of the Eclipse Public License v1.0
|
|
6
|
+
# which accompanies this distribution, and is available at
|
|
7
|
+
# http://www.opensource.org/licenses/eclipse-1.0.php
|
|
8
|
+
#
|
|
9
|
+
# Contributors:
|
|
10
|
+
# Gareth Lewis - Initial contribution.
|
|
11
|
+
# Intuit Partner Platform.
|
|
12
|
+
#++#####################################################################
|
|
13
|
+
require 'QuickBaseClient'
|
|
14
|
+
|
|
15
|
+
module QuickBase
|
|
16
|
+
|
|
17
|
+
=begin rdoc
|
|
18
|
+
|
|
19
|
+
The data file format processed by this class is:-
|
|
20
|
+
|
|
21
|
+
application: <application name>
|
|
22
|
+
table: <table name>
|
|
23
|
+
dbid: <dbid>
|
|
24
|
+
record: [record id#]
|
|
25
|
+
<field name>: value
|
|
26
|
+
<field name>: value
|
|
27
|
+
...
|
|
28
|
+
record:
|
|
29
|
+
<field name>: value
|
|
30
|
+
<field name>: value
|
|
31
|
+
|
|
32
|
+
1) application:, table:, dbid:, record: <field name>: must appear at the beginning of a line.
|
|
33
|
+
2) Except for text fields, whitespace will be trimmed from field values.
|
|
34
|
+
3) Lines starting with whitespace are processed as data for a multi-line text field.
|
|
35
|
+
The first whitespace character will be ignored.
|
|
36
|
+
4) If <application name> is followed by 'record:' , the table is assumed to have the
|
|
37
|
+
same name as the application, as in a single-table application.
|
|
38
|
+
5) <dbid> will change the target/source table for any subsequent 'record:'.
|
|
39
|
+
When possible, use <dbid> instead of <application name> and <table name>.
|
|
40
|
+
6) If <application name> refers to a non-existent application, the <application name> will be created
|
|
41
|
+
as a new single-table application and <table name> will be ignored.
|
|
42
|
+
7) If <field name>: is not an existing field and is followed by a valid field type name, such as 'date',
|
|
43
|
+
the <field name> field wil be added to the QuickBase table. Field properties can appear after the
|
|
44
|
+
field type.
|
|
45
|
+
8) If <field name> is a number and is not an existing field, the field will be treated as a QuickBase field id.
|
|
46
|
+
9) If record: is followed by a number, the number is assumed to be the id of an existing record, and data
|
|
47
|
+
in subsequent <field name>: will overwite existing fields when sent to QuickBase.
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
|
|
52
|
+
application:Email Inbox
|
|
53
|
+
table:Messages
|
|
54
|
+
record:
|
|
55
|
+
From: my@email.com
|
|
56
|
+
Date Sent: 12/31/2007
|
|
57
|
+
Subject:Testing TextData class
|
|
58
|
+
Body: This is a test of the TextData class.
|
|
59
|
+
This is a multi-line text field.
|
|
60
|
+
|
|
61
|
+
=end
|
|
62
|
+
|
|
63
|
+
# Class to read and write human-editable text files of data that can be sent to QuickBase.
|
|
64
|
+
# The file format can also be used as a simple intermediate format for getting data into
|
|
65
|
+
# QuickBase programmatically from other sources. This format is better than CSV for
|
|
66
|
+
# human-readability and allows fields to be skipped and to appear in any sequence.
|
|
67
|
+
# The format is like yaml, probably isn't a subset of it, but is simpler.
|
|
68
|
+
class TextData
|
|
69
|
+
|
|
70
|
+
attr_reader :dbid
|
|
71
|
+
|
|
72
|
+
def initialize(username,password)
|
|
73
|
+
@username,@password=username,password
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Read a file in the format described above and send the data to QuickBase.
|
|
77
|
+
# Records are added or edited when tables, fields and values have been validated
|
|
78
|
+
# and all the data for a record has been accumulated.
|
|
79
|
+
# Any error in the input file will stop further processing of the file.
|
|
80
|
+
def sendDataToQuickBase(inputFilename, errorFilename)
|
|
81
|
+
@errorFilename = errorFilename
|
|
82
|
+
if FileTest.exist?(inputFilename)
|
|
83
|
+
if login
|
|
84
|
+
lineNumber = 1
|
|
85
|
+
begin
|
|
86
|
+
IO.foreach(inputFilename){|line|
|
|
87
|
+
if line.index( "application:") == 0
|
|
88
|
+
line.sub!("application:","")
|
|
89
|
+
line.strip!
|
|
90
|
+
setApplication(line)
|
|
91
|
+
elsif line.index( "table:") == 0
|
|
92
|
+
line.sub!("table:","")
|
|
93
|
+
line.strip!
|
|
94
|
+
setTable(line)
|
|
95
|
+
elsif line.index( "dbid:") == 0
|
|
96
|
+
line.sub!("dbid:","")
|
|
97
|
+
line.strip!
|
|
98
|
+
setDBID(line)
|
|
99
|
+
elsif line.index( "record:") == 0
|
|
100
|
+
line.sub!("record:","")
|
|
101
|
+
line.strip!
|
|
102
|
+
if line.match(/[0-9]+/)
|
|
103
|
+
setRecord(line)
|
|
104
|
+
else
|
|
105
|
+
setRecord(nil)
|
|
106
|
+
end
|
|
107
|
+
elsif line.match( /^([^:]+):(.*)$/)
|
|
108
|
+
setFieldValue($1, $2)
|
|
109
|
+
elsif line.match( /^([\s\t])(.*)$/)
|
|
110
|
+
appendFieldValue($2)
|
|
111
|
+
end
|
|
112
|
+
lineNumber = lineNumber + 1
|
|
113
|
+
}
|
|
114
|
+
addOrEditRecord
|
|
115
|
+
rescue StandardError => error
|
|
116
|
+
writeError("File #{inputFilename}, line #{lineNumber}: #{error}.")
|
|
117
|
+
end
|
|
118
|
+
else
|
|
119
|
+
writeError("Error connecting to QuickBase.")
|
|
120
|
+
end
|
|
121
|
+
logout
|
|
122
|
+
else
|
|
123
|
+
writeError("Input file #{inputFilename} does not exist.")
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def writeError(error)
|
|
128
|
+
@errorFile = File.new(@errorFilename,"w") if @errorFilename and @errorFile.nil?
|
|
129
|
+
@errorFile.puts(error)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def login
|
|
133
|
+
if @username.nil? or @password.nil?
|
|
134
|
+
writeError("Must set username and password")
|
|
135
|
+
elsif @qbc.nil?
|
|
136
|
+
@qbc = QuickBase::Client.new(@username,@password)
|
|
137
|
+
end
|
|
138
|
+
ret = @qbc and @qbc.requestSucceeded
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def logout
|
|
142
|
+
@qbc.signOut if @qbc
|
|
143
|
+
@qbc = nil
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def resetTableVars
|
|
147
|
+
@activeRecordNumber = nil
|
|
148
|
+
@activeFieldID = nil
|
|
149
|
+
@activeField = nil
|
|
150
|
+
@fieldIDValues = nil
|
|
151
|
+
@fieldValues = nil
|
|
152
|
+
@fieldType = nil
|
|
153
|
+
@fieldProperties = nil
|
|
154
|
+
@fieldAllowsNewChoices = false
|
|
155
|
+
@fieldIsMultiLineText = false
|
|
156
|
+
@fieldIsValidFileAttachment = false
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def setApplication(appName)
|
|
160
|
+
addOrEditRecord
|
|
161
|
+
resetTableVars
|
|
162
|
+
@singleTableApplication = false
|
|
163
|
+
@qbc.findDBByname(appName)
|
|
164
|
+
if @qbc.dbid.nil?
|
|
165
|
+
@qbc.createDatabase(appName,appName)
|
|
166
|
+
raise "Error creating application '#{name}'" if @qbc.dbid.nil?
|
|
167
|
+
raise "Unable to find table '#{@appName}'" if !@qbc.lookupChdbid(@appName)
|
|
168
|
+
@singleTableApplication = true
|
|
169
|
+
end
|
|
170
|
+
raise "Error finding or creating application '#{name}'" if @qbc.dbid.nil?
|
|
171
|
+
@appName = appName
|
|
172
|
+
@qbc._getSchema
|
|
173
|
+
@dbid = @qbc.dbid.dup
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def setTable(name)
|
|
177
|
+
addOrEditRecord
|
|
178
|
+
resetTableVars
|
|
179
|
+
if !@singleTableApplication
|
|
180
|
+
raise "Unable to find table '#{name}'" if !@qbc.lookupChdbid(name)
|
|
181
|
+
@qbc._getSchema
|
|
182
|
+
end
|
|
183
|
+
@dbid = @qbc.dbid.dup
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def setDBID(id)
|
|
187
|
+
addOrEditRecord
|
|
188
|
+
resetTableVars
|
|
189
|
+
@singleTableApplication = false
|
|
190
|
+
@qbc.getSchema(id)
|
|
191
|
+
raise "Unable to find table with the '#{id}' id" if @qbc.dbid.nil?
|
|
192
|
+
@dbid = @qbc.dbid.dup
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def setRecord(number)
|
|
196
|
+
addOrEditRecord
|
|
197
|
+
@activeRecordNumber = number
|
|
198
|
+
@activeRecordNumber = nil if @activeRecordNumber and @activeRecordNumber.length == 0
|
|
199
|
+
if @activeRecordNumber
|
|
200
|
+
@qbc._setActiveRecord(@activeRecordNumber)
|
|
201
|
+
else
|
|
202
|
+
@qbc.resetrid
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def checkFieldNameAndValue(fieldName,fieldValue)
|
|
207
|
+
|
|
208
|
+
@fieldNameIsFieldID = false
|
|
209
|
+
@fieldAllowsNewChoices = false
|
|
210
|
+
@fieldIsMultiLineText = false
|
|
211
|
+
@fieldIsValidFileAttachment = false
|
|
212
|
+
@fieldType = nil
|
|
213
|
+
@fieldProperties = nil
|
|
214
|
+
|
|
215
|
+
fieldElement = @qbc.lookupField( @qbc.lookupFieldIDByName(fieldName) )
|
|
216
|
+
if !fieldElement and fieldName.match(/[0-9]+/) # if it's a number, see if it's a valid field id
|
|
217
|
+
fieldElement = @qbc.lookupField(fieldName,fieldValue)
|
|
218
|
+
@fieldNameIsFieldID = true if fieldElement
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
if !fieldElement # field doesn't exist. the field can be created if the value is actually a valid field type
|
|
222
|
+
if fieldValue and fieldValue.length > 0
|
|
223
|
+
fieldValue.strip!
|
|
224
|
+
if fieldValue.length > 0
|
|
225
|
+
fieldTypeAndProperties = fieldValue.split(/ /)
|
|
226
|
+
type = fieldTypeAndProperties[0]
|
|
227
|
+
if @qbc.isValidFieldType?(type)
|
|
228
|
+
@fieldType = Hash.new if @fieldType.nil?
|
|
229
|
+
@fieldType[fieldName] = type.dup
|
|
230
|
+
fieldTypeAndProperties.shift # anything after the field type must be valid field properties in the form property="value"
|
|
231
|
+
fieldTypeAndProperties.each { |property|
|
|
232
|
+
propertyType = property[0..(property.index("=")-1)]
|
|
233
|
+
raise "Invalid field property type #{propertyType}" if !@qbc.isValidFieldProperty?(propertyType)
|
|
234
|
+
propertyValue = property[(property.index("=")+1)..(property.rindex("\"")-1)]
|
|
235
|
+
raise "Missing value for field property #{propertyType}" if propertyValue.nil? or propertyValue.length == 0
|
|
236
|
+
@fieldProperties = Hash.new if @fieldProperties.nil?
|
|
237
|
+
propertyAndValuePair = Hash.new
|
|
238
|
+
propertyAndValuePair[propertyType]=propertyValue
|
|
239
|
+
@fieldProperties[fieldName] << propertyAndValuePair
|
|
240
|
+
}
|
|
241
|
+
else
|
|
242
|
+
raise "Invalid field type #{type}."
|
|
243
|
+
end
|
|
244
|
+
else
|
|
245
|
+
raise "Invalid field name #{fieldName}."
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
else
|
|
249
|
+
# do basic data type check
|
|
250
|
+
type = fieldElement.attributes["field_type"].dup
|
|
251
|
+
raise "Unable to determine data type for #{fieldName} field" if type.nil?
|
|
252
|
+
if fieldValue.length > 0 #any field can be blanked out
|
|
253
|
+
case type
|
|
254
|
+
when "checkbox"
|
|
255
|
+
if !(fieldValue == "1" or fieldValue == "0")
|
|
256
|
+
raise "Invalid data '#{fieldValue}' for checkbox field #{fieldName}"
|
|
257
|
+
end
|
|
258
|
+
when "date"
|
|
259
|
+
fieldValue.gsub!("/","-")
|
|
260
|
+
if !fieldValue.match(/[0-9][0-9]\-[0-9][0-9]\-[0-9][0-9][0-9][0-9]/)
|
|
261
|
+
raise "Invalid data '#{fieldValue}' for date field #{fieldName}"
|
|
262
|
+
end
|
|
263
|
+
when "duration", "float", "currency", "rating"
|
|
264
|
+
fieldValue.gsub!(",","")
|
|
265
|
+
if !fieldValue.match(/[0-9]*\.?[0-9]*/)
|
|
266
|
+
raise "Invalid data '#{fieldValue}' for field #{fieldName}"
|
|
267
|
+
end
|
|
268
|
+
when "phone"
|
|
269
|
+
if !fieldValue.match(/[0-9|\.|x]+/)
|
|
270
|
+
raise "Invalid data '#{fieldValue}' for phone field #{fieldName}"
|
|
271
|
+
end
|
|
272
|
+
when "file"
|
|
273
|
+
if FileTest.exist?(fieldValue)
|
|
274
|
+
@fieldIsValidFileAttachment = true
|
|
275
|
+
else
|
|
276
|
+
raise "Invalid file name '#{fieldValue}' for file attachment field #{fieldName}"
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
# check whether field allows user to add to a choicelist.
|
|
281
|
+
@fieldAllowsNewChoices = fieldAllowsNewChoices?(fieldElement)
|
|
282
|
+
# check whether field allows mutliple lines.
|
|
283
|
+
@fieldIsMultiLineText = fieldIsMultiLineText?(fieldElement)
|
|
284
|
+
end
|
|
285
|
+
fieldElement
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def fieldAllowsNewChoices?(fieldElement)
|
|
289
|
+
allowsUserChoices = false
|
|
290
|
+
findPropertyBlock = proc { |element|
|
|
291
|
+
if element.is_a?(REXML::Element) and element.name == "allow_new_choices" and element.has_text?
|
|
292
|
+
allowsUserChoices = (element.text == "1")
|
|
293
|
+
end
|
|
294
|
+
}
|
|
295
|
+
@qbc.processChildElements(fieldElement, true, findPropertyBlock)
|
|
296
|
+
allowsUserChoices
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def fieldIsMultiLineText?(fieldElement)
|
|
300
|
+
isMultiLineText = false
|
|
301
|
+
findPropertyBlock = proc { |element|
|
|
302
|
+
if element.is_a?(REXML::Element) and element.name == "num_lines" and element.has_text?
|
|
303
|
+
isMultiLineText = (element.text != "1")
|
|
304
|
+
end
|
|
305
|
+
}
|
|
306
|
+
@qbc.processChildElements(fieldElement, true, findPropertyBlock)
|
|
307
|
+
isMultiLineText
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def addField(field,type,properties)
|
|
311
|
+
newFieldID, newfieldLabel = @qbc._addField(field, type)
|
|
312
|
+
if newFieldID
|
|
313
|
+
if properties
|
|
314
|
+
fieldPropertiesToSet = Hash.new
|
|
315
|
+
properties.each{|property|
|
|
316
|
+
property.each{|propertyName,propertyValue| fieldPropertiesToSet[propertyName] = propertyValue }
|
|
317
|
+
}
|
|
318
|
+
@qbc._setFieldProperties(fieldPropertiesToSet,newFieldID)
|
|
319
|
+
@qbc._getSchema
|
|
320
|
+
end
|
|
321
|
+
else
|
|
322
|
+
raise "Error creating new field #{field}"
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def setFieldValue(fieldName, value)
|
|
327
|
+
if checkFieldNameAndValue(fieldName,value)
|
|
328
|
+
if @fieldNameIsFieldID
|
|
329
|
+
@activeFieldID = fieldName
|
|
330
|
+
@activeField = nil
|
|
331
|
+
@fieldIDValues = Hash.new if @fieldIDValues.nil?
|
|
332
|
+
@fieldIDValues[@activeFieldID] = value.dup
|
|
333
|
+
@fieldIDProperties = Hash.new if @fieldIDProperties.nil?
|
|
334
|
+
@fieldIDProperties[@activeFieldID] = { "fieldAllowsNewChoices"=>@fieldAllowsNewChoices, "fieldIsMultiLineText"=>@fieldIsMultiLineText, "fieldIsValidFileAttachment"=>@fieldIsValidFileAttachment }
|
|
335
|
+
else
|
|
336
|
+
@activeField = fieldName
|
|
337
|
+
@activeFieldID = nil
|
|
338
|
+
@fieldValues = Hash.new if @fieldValues.nil?
|
|
339
|
+
@fieldValues[@activeField] = value.dup
|
|
340
|
+
@fieldProperties = Hash.new if @fieldProperties.nil?
|
|
341
|
+
@fieldProperties[@activeField] = { "fieldAllowsNewChoices"=>@fieldAllowsNewChoices,"fieldIsMultiLineText"=>@fieldIsMultiLineText, "fieldIsValidFileAttachment"=>@fieldIsValidFileAttachment }
|
|
342
|
+
end
|
|
343
|
+
elsif @fieldType and @fieldType[fieldName]
|
|
344
|
+
if @fieldProperties and @fieldProperties[fieldName]
|
|
345
|
+
addField(fieldName,@fieldType[fieldName],@fieldProperties[fieldName])
|
|
346
|
+
else
|
|
347
|
+
addField(fieldName,@fieldType[fieldName],nil)
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def appendFieldValue(value)
|
|
353
|
+
if value and value.length > 0
|
|
354
|
+
if @activeField and @fieldProperties[@activeField]["fieldIsMultiLineText"]
|
|
355
|
+
@fieldValues = Hash.new if @fieldValues.nil?
|
|
356
|
+
@fieldValues[@activeField] << "\n"
|
|
357
|
+
@fieldValues[@activeField] << value.dup
|
|
358
|
+
elsif @activeFieldID and @fieldIDProperties[@activeFieldID]["fieldIsMultiLineText"]
|
|
359
|
+
@fieldIDValues = Hash.new if @fieldIDValues.nil?
|
|
360
|
+
@fieldIDValues[@activeFieldID] << "\n"
|
|
361
|
+
@fieldIDValues[@activeFieldID] << value.dup
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def addFieldValuePairs
|
|
367
|
+
@qbc.clearFieldValuePairList
|
|
368
|
+
if @fieldValues
|
|
369
|
+
@fieldValues.each{ |f,v|
|
|
370
|
+
if f and v and @fieldProperties and @fieldProperties[f] and @fieldProperties[f]["fieldAllowsNewChoices"]
|
|
371
|
+
@fieldChoicesToSet = Hash.new if @fieldChoicesToSet.nil?
|
|
372
|
+
@fieldChoicesToSet[f] = Array.new if @fieldChoicesToSet[f].nil?
|
|
373
|
+
@fieldChoicesToSet[f] << v
|
|
374
|
+
end
|
|
375
|
+
if f and v and @fieldProperties and @fieldProperties[f] and @fieldProperties[f]["fieldIsValidFileAttachment"]
|
|
376
|
+
@qbc.addFieldValuePair(f.dup,nil,v.dup,nil)
|
|
377
|
+
else
|
|
378
|
+
@qbc.addFieldValuePair(f.dup,nil,nil,v.dup)
|
|
379
|
+
end
|
|
380
|
+
}
|
|
381
|
+
@fieldValues = nil
|
|
382
|
+
end
|
|
383
|
+
if @fieldIDValues
|
|
384
|
+
@fieldIDValues.each{ |f,v|
|
|
385
|
+
if @fieldIDProperties and @fieldIDProperties[f] and @fieldIDProperties[f]["fieldAllowsNewChoices"]
|
|
386
|
+
@fieldIDChoicesToSet = Hash.new if @fieldChoicesToSet.nil?
|
|
387
|
+
@fieldIDChoicesToSet[f] << v
|
|
388
|
+
end
|
|
389
|
+
if @fieldIDProperties and @fieldIDProperties[f] and @fieldIDProperties[f]["fieldIsValidFileAttachment"]
|
|
390
|
+
@qbc.addFieldValuePair(nil,f.dup,v.dup,nil)
|
|
391
|
+
else
|
|
392
|
+
@qbc.addFieldValuePair(nil,f.dup,nil,v.dup)
|
|
393
|
+
end
|
|
394
|
+
}
|
|
395
|
+
@fieldIDValues = nil
|
|
396
|
+
end
|
|
397
|
+
@qbc.fvlist
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def isValidRecord?(addingRecord)
|
|
401
|
+
valid = true
|
|
402
|
+
if addingRecord and @qbc.fields
|
|
403
|
+
# check that all required fields are present
|
|
404
|
+
requiredFieldIDs = Hash.new
|
|
405
|
+
requiredFields = Hash.new
|
|
406
|
+
fieldID = ""
|
|
407
|
+
fieldName = ""
|
|
408
|
+
findRequiredFieldsBlock = proc { |element|
|
|
409
|
+
if element.is_a?(REXML::Element) and element.name == "field"
|
|
410
|
+
fieldID = element.attributes["id"]
|
|
411
|
+
elsif element.is_a?(REXML::Element) and element.name == "label" and element.has_text?
|
|
412
|
+
fieldName = element.text
|
|
413
|
+
elsif element.is_a?(REXML::Element) and element.name == "required" and element.has_text?
|
|
414
|
+
if element.text == "1"
|
|
415
|
+
requiredFieldIDs[fieldID] = "1"
|
|
416
|
+
requiredFields[fieldName] = "1"
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
}
|
|
420
|
+
@qbc.processChildElements(@qbc.fields, false, findRequiredFieldsBlock)
|
|
421
|
+
|
|
422
|
+
if @fieldValues
|
|
423
|
+
@fieldValues.each{ |f,v| requiredFields[f] = nil if requiredFields[f] }
|
|
424
|
+
end
|
|
425
|
+
if @fieldIDValues
|
|
426
|
+
@fieldIDValues.each{ |f,v| requiredFieldIDs[f] = nil if requiredFieldIDs[f] }
|
|
427
|
+
end
|
|
428
|
+
missingFields = ""
|
|
429
|
+
requiredFields.each{ |f,v| missingFields << "#{f} " if v } if @fieldValues
|
|
430
|
+
requiredFieldIDs.each{ |f,v| missingFields << "#{f} " if v } if @fieldIDValues
|
|
431
|
+
raise "Required fields are missing: #{missingFields}" if missingFields.length > 0
|
|
432
|
+
end
|
|
433
|
+
valid
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def addOrEditRecord
|
|
437
|
+
if @activeRecordNumber
|
|
438
|
+
editRecord
|
|
439
|
+
elsif isValidRecord?(true)
|
|
440
|
+
addRecord
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def addRecord
|
|
445
|
+
if addFieldValuePairs
|
|
446
|
+
addFieldChoices
|
|
447
|
+
@qbc.addRecord(@qbc.dbid, @qbc.fvlist)
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
def editRecord
|
|
452
|
+
if @activeRecordNumber and addFieldValuePairs
|
|
453
|
+
addFieldChoices
|
|
454
|
+
@qbc.editRecord(@qbc.dbid, @activeRecordNumber, @qbc.fvlist)
|
|
455
|
+
@activeRecordNumber = nil
|
|
456
|
+
end
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
def addFieldChoices
|
|
460
|
+
if @fieldIDChoicesToSet
|
|
461
|
+
@fieldIDChoicesToSet.each{ |f,choices|
|
|
462
|
+
@qbc._fieldAddChoices(f,choices)
|
|
463
|
+
}
|
|
464
|
+
@fieldIDChoicesToSet = nil
|
|
465
|
+
end
|
|
466
|
+
if @fieldChoicesToSet
|
|
467
|
+
@fieldChoicesToSet.each{ |f,choices|
|
|
468
|
+
@qbc._fieldNameAddChoices(f,choices)
|
|
469
|
+
}
|
|
470
|
+
@fieldChoicesToSet = nil
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
# Writes all records and all fields from a table to the specified
|
|
475
|
+
# outputFilename, in the format specified above, sorted by record ID#.
|
|
476
|
+
# Since they can't be sent back into QuickBase, built-in fields are excluded.
|
|
477
|
+
def writeDataFromQuickBase(dbid, outputFilename, errorFilename)
|
|
478
|
+
@errorFilename = errorFilename
|
|
479
|
+
if login
|
|
480
|
+
@qbc.getSchema(dbid)
|
|
481
|
+
if @qbc.dbid and @qbc.requestSucceeded
|
|
482
|
+
@outputFile = File.new(outputFilename,"w")
|
|
483
|
+
@outputFile.puts( "dbid:#{dbid}")
|
|
484
|
+
@isBuitlInField = false
|
|
485
|
+
recordIDs = @qbc.getAllValuesForFields(dbid,["Record ID#"],nil,nil,nil,"3","3")
|
|
486
|
+
if recordIDs
|
|
487
|
+
recordIDs["Record ID#"].each{ |recordID|
|
|
488
|
+
@outputFile.puts( "record:#{recordID}")
|
|
489
|
+
@qbc._getRecordInfo(recordID)
|
|
490
|
+
processFieldDataBlock = proc { |element|
|
|
491
|
+
if element.is_a?(REXML::Element)
|
|
492
|
+
if element.name == "fid"
|
|
493
|
+
@isBuitlInField = element.text.to_i < 6
|
|
494
|
+
elsif element.name == "name" and !@isBuitlInField
|
|
495
|
+
@outputFile.print("#{element.text}:")
|
|
496
|
+
elsif element.name == "type"
|
|
497
|
+
@outputFieldType = element.text.dup.downcase!
|
|
498
|
+
elsif element.name == "value" and !@isBuitlInField
|
|
499
|
+
outputFieldValue = ""
|
|
500
|
+
outputFieldValue = element.text.dup if element.has_text?
|
|
501
|
+
outputFieldValue.gsub!("<BR/>","\n ")
|
|
502
|
+
outputFieldValue = @qbc.formatFieldValue(outputFieldValue,@outputFieldType)
|
|
503
|
+
outputFieldValue = "" if outputFieldValue.nil?
|
|
504
|
+
@outputFile.puts(outputFieldValue)
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
}
|
|
508
|
+
@qbc.processChildElements(@qbc.field_data_list,true,processFieldDataBlock)
|
|
509
|
+
}
|
|
510
|
+
@outputFile.close
|
|
511
|
+
end
|
|
512
|
+
else
|
|
513
|
+
writeError("Invalid dbid #{dbid}.")
|
|
514
|
+
end
|
|
515
|
+
logout
|
|
516
|
+
else
|
|
517
|
+
writeError("Error connecting to QuickBase.")
|
|
518
|
+
end
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
def TextData.uploadData(username,password,file)
|
|
522
|
+
td = QuickBase::TextData.new(username,password)
|
|
523
|
+
td.sendDataToQuickBase(file,"textDataUploadErrors.txt")
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
def TextData.downloadData(username,password,dbid)
|
|
527
|
+
td = QuickBase::TextData.new(username,password)
|
|
528
|
+
td.writeDataFromQuickBase(dbid,"downloadedTextData.txt","textDataDownloadErrors.txt")
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
# Sends data from file to QuickBase, then overwrites the same
|
|
532
|
+
# file with data from the last referenced table. It's best to
|
|
533
|
+
# use this for synchronizing just one table's data.
|
|
534
|
+
def TextData.synchDataFile(username,password,file)
|
|
535
|
+
td = QuickBase::TextData.new(username,password)
|
|
536
|
+
td.sendDataToQuickBase(file,"textDataUploadErrors.txt")
|
|
537
|
+
td.writeDataFromQuickBase(td.dbid,file,"textDataDownloadErrors.txt")
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
end # class TextData
|
|
541
|
+
|
|
542
|
+
end # module QuickBase
|
|
543
|
+
|
|
544
|
+
#QuickBase::TextData.uploadData(ARGV[0],ARGV[1],ARGV[2]) if ARGV[2]
|
|
545
|
+
#QuickBase::TextData.downloadData(ARGV[0],ARGV[1],ARGV[3]) if ARGV[3]
|