sfkb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0fe296d4e57e23a2b3b1aa70da575f8fec6e8d5d7b0584e46209b202af5fcb36
4
+ data.tar.gz: 11771d874f39e8a1f80cd98ce90f578cbf7d8c0d3653f9c10981725cb542922a
5
+ SHA512:
6
+ metadata.gz: 3bb0f074fd40c5fc50ec0696c7d8b5de4d846571cf48921aabe889ab994adac32702f342ba9e2a4fb57e47ca4ba6f45b84750f1861bfd87e51bcb64a107d9655
7
+ data.tar.gz: dd1f38d22b8a373491f7a3c72b3d6ddad8be5794574368e529a493761ece1c2dd51f5ac74c8a5240794cf1de8af9e89b7d0d7ba4d8f1f5cfb4133a8441013a2a
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 JJ Buckley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,106 @@
1
+ # SFKB
2
+
3
+ SalesForce Knowledge Base
4
+
5
+ A Ruby library that extends [Restforce][] to provide convenient access to
6
+ Articles and ArticlesVersions in the Salesforce Knowledge Base.
7
+
8
+ [![Build Status](https://travis-ci.org/bjjb/sfkb.svg?branch=master)](https://travis-ci.org/bjjb/sfkb)
9
+
10
+ ## Requirements
11
+
12
+ - [Ruby][]
13
+ - [SalesForce][]
14
+
15
+ ## Installation
16
+
17
+ gem install sfkb
18
+
19
+ ## Usage
20
+
21
+ Include it in your project, for example in your Gemfile
22
+
23
+ ```ruby
24
+ gem 'sfkb'
25
+ ```
26
+
27
+ or just require it.
28
+
29
+ Now you can use the module as a client, or do something fancier by
30
+ instantiating a `SFKB::Client`.
31
+
32
+ For example, here's how to print the titles of the master versions of the
33
+ first 10 documents:
34
+
35
+ ```
36
+ require 'sfkb'
37
+ SFKB.new.articles.take(10).each { |a| puts a.OnlineMasterVersion.data.Title }
38
+ ```
39
+
40
+ Here's how to list the titles of every translation of every draft article:
41
+ ```
42
+ sf = SFKB.new
43
+ sf.articles.select(:hasTranslations?) do |a|
44
+ sf.translations(a, 'Draft').each do |t|
45
+ puts t.data.Title
46
+ end
47
+ end
48
+ ```
49
+
50
+ Documentation is available [here][docs].
51
+
52
+ You will need to register an app in your Salesforce instance so you can obtain
53
+ your `SALESFORCE_CLIENT_ID` and `SALESFORCE_CLIENT_SECRET` (see below).
54
+
55
+ ## Contributing
56
+
57
+ Bug reports and pull requests are welcome on GitHub at
58
+ https://github.com/bjjb/sfkb. This project is intended to be a safe,
59
+ welcoming space for collaboration, and contributors are expected to adhere to
60
+ the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
61
+
62
+ ## Testing
63
+
64
+ When testing, you'll want to preconfigure your environment with these
65
+ settings:
66
+
67
+ - `SALESFORCE_CLIENT_ID`
68
+ - `SALESFORCE_CLIENT_SECRET`
69
+
70
+ You might also want to set the following (which defaults to
71
+ 'login.salesforce.com) to something like 'company.my.salesforce.com', or
72
+ 'test.salesforce.com' if you want to use a sandbox.
73
+
74
+ - `SALESFORCE_HOST`
75
+
76
+ For authentication, you may either use the username/password/token-secret
77
+ combination:
78
+
79
+ - `SALESFORCE_USERNAME`
80
+ - `SALESFORCE_PASSWORD`
81
+ - `SALESFORCE_SECURITY_TOKEN`
82
+
83
+ or
84
+
85
+ - `SALESFORCE_OAUTH_TOKEN`
86
+ - `SALESFORCE_REFRESH_TOKEN`
87
+
88
+ the latter of which is optional, and can (and probably should) be set using
89
+ the [`authentication_callback`][1]. For more details, see [Restforce][].
90
+
91
+ ## License
92
+
93
+ The gem is available as open source under the terms of the [MIT
94
+ License](https://opensource.org/licenses/MIT).
95
+
96
+ ## Code of Conduct
97
+
98
+ Everyone interacting in the Glinga project’s codebases, issue trackers, chat
99
+ rooms and mailing lists is expected to follow the [code of
100
+ conduct](https://gitlab.com/bjjb/glinga/blob/master/CODE_OF_CONDUCT.md).
101
+
102
+ [Restforce]: https://github.com/restforce/restforce
103
+ [Ruby]: https://ruby-lang.org
104
+ [Salesforce]: https://salesforce.com
105
+ [docs]: http://www.rubydoc.info/github/bjjb/sfkb
106
+ [1]: https://github.com/restforce/restforce#oauth-token-authentication
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'bundle' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "rubygems"
12
+
13
+ m = Module.new do
14
+ module_function
15
+
16
+ def invoked_as_script?
17
+ File.expand_path($0) == File.expand_path(__FILE__)
18
+ end
19
+
20
+ def env_var_version
21
+ ENV["BUNDLER_VERSION"]
22
+ end
23
+
24
+ def cli_arg_version
25
+ return unless invoked_as_script? # don't want to hijack other binstubs
26
+ return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
27
+ bundler_version = nil
28
+ update_index = nil
29
+ ARGV.each_with_index do |a, i|
30
+ if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
31
+ bundler_version = a
32
+ end
33
+ next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
34
+ bundler_version = $1 || ">= 0.a"
35
+ update_index = i
36
+ end
37
+ bundler_version
38
+ end
39
+
40
+ def gemfile
41
+ gemfile = ENV["BUNDLE_GEMFILE"]
42
+ return gemfile if gemfile && !gemfile.empty?
43
+
44
+ File.expand_path("../../Gemfile", __FILE__)
45
+ end
46
+
47
+ def lockfile
48
+ lockfile =
49
+ case File.basename(gemfile)
50
+ when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
51
+ else "#{gemfile}.lock"
52
+ end
53
+ File.expand_path(lockfile)
54
+ end
55
+
56
+ def lockfile_version
57
+ return unless File.file?(lockfile)
58
+ lockfile_contents = File.read(lockfile)
59
+ return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
60
+ Regexp.last_match(1)
61
+ end
62
+
63
+ def bundler_version
64
+ @bundler_version ||= begin
65
+ env_var_version || cli_arg_version ||
66
+ lockfile_version || "#{Gem::Requirement.default}.a"
67
+ end
68
+ end
69
+
70
+ def load_bundler!
71
+ ENV["BUNDLE_GEMFILE"] ||= gemfile
72
+
73
+ # must dup string for RG < 1.8 compatibility
74
+ activate_bundler(bundler_version.dup)
75
+ end
76
+
77
+ def activate_bundler(bundler_version)
78
+ if Gem::Version.correct?(bundler_version) && Gem::Version.new(bundler_version).release < Gem::Version.new("2.0")
79
+ bundler_version = "< 2"
80
+ end
81
+ gem_error = activation_error_handling do
82
+ gem "bundler", bundler_version
83
+ end
84
+ return if gem_error.nil?
85
+ require_error = activation_error_handling do
86
+ require "bundler/version"
87
+ end
88
+ return if require_error.nil? && Gem::Requirement.new(bundler_version).satisfied_by?(Gem::Version.new(Bundler::VERSION))
89
+ warn "Activating bundler (#{bundler_version}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_version}'`"
90
+ exit 42
91
+ end
92
+
93
+ def activation_error_handling
94
+ yield
95
+ nil
96
+ rescue StandardError, LoadError => e
97
+ e
98
+ end
99
+ end
100
+
101
+ m.load_bundler!
102
+
103
+ if m.invoked_as_script?
104
+ load Gem.bin_path("bundler", "bundle")
105
+ end
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'byebug' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("byebug", "byebug")
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'coderay' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("coderay", "coderay")
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "sfkb"
5
+
6
+ require "pry"
7
+ Pry.start
8
+
9
+ require "irb"
10
+ IRB.start(__FILE__)
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'dotenv' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("dotenv", "dotenv")
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'listen' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("listen", "listen")
data/bin/pry ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'pry' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("pry", "pry")
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rackup' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rack", "rackup")
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rake' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rake", "rake")
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rerun' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rerun", "rerun")
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'safe_yaml' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("safe_yaml", "safe_yaml")
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install --binstubs
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'sfkb' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("sfkb", "sfkb")
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'sfkb'
4
+
5
+ begin
6
+ h = SFKB.new.authenticate!
7
+ puts 'Everything seems OK... here is an access token'
8
+ puts h.access_token
9
+ rescue
10
+ puts "Error contacting Salesforce. Check your credentials. See here:"
11
+ puts "https://github.com/bjjb/sfkb"
12
+ end
@@ -0,0 +1,13 @@
1
+ require 'restforce'
2
+ require 'dotenv/load'
3
+
4
+ # A helper library for using the Salesforce Knowledge base in Ruby.
5
+ module SFKB
6
+ autoload :Knowledge, 'sfkb/knowledge'
7
+ autoload :Client, 'sfkb/client'
8
+ autoload :REST, 'sfkb/rest'
9
+
10
+ def self.new(*args)
11
+ Client.new(*args)
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ require 'restforce/data/client'
2
+ require 'sfkb/rest'
3
+ require 'sfkb/knowledge'
4
+
5
+ # An SFKB::Client is an object with a connection to salesforce that can lookup
6
+ # KnowledgeArticles and KnowledgeArticleVersions using the Salesforce REST
7
+ # API.
8
+ class SFKB::Client < Restforce::Data::Client
9
+ include SFKB::REST
10
+ include SFKB::Knowledge
11
+ end
@@ -0,0 +1,39 @@
1
+ module SFKB
2
+ # Methods to smartly apply singleton methods to REST objects
3
+ module Decoration
4
+ def decorate(object)
5
+ return object unless block_given?
6
+ yield object
7
+ object
8
+ end
9
+
10
+ def define_link(object, name, url, &block)
11
+ getter = -> (url) { get(url).body }
12
+ decorator = -> (o) { decorate(o, &block) }
13
+ object.define_singleton_method(name) { decorator.call(getter.call(url)) }
14
+ end
15
+
16
+ def define_links(object, urls, &block)
17
+ urls.each { |k, v| define_link(object, k, v, &block) }
18
+ end
19
+
20
+ def define_predicate(object, name, value)
21
+ object.define_singleton_method(:"#{name}?") { value }
22
+ end
23
+
24
+ def autodefine(object)
25
+ return unless object.respond_to?(:additionalInformation)
26
+ return unless info = object.additionalInformation
27
+ info.each do |k, v|
28
+ if [true, false].include?(v)
29
+ define_predicate(object, k, v)
30
+ elsif k.to_s == 'data'
31
+ define_link(object, :data, v)
32
+ elsif k.to_s == 'urls'
33
+ define_links(object, v) { |o| autodefine(o) }
34
+ end
35
+ end
36
+ object
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,32 @@
1
+ require 'sfkb/rest'
2
+ require 'sfkb/settings'
3
+ require 'sfkb/decoration'
4
+
5
+ module SFKB
6
+ module Knowledge
7
+ include REST
8
+ include Settings
9
+ include Decoration
10
+
11
+ # Queries for all (undeleted) article IDs, returning an array.
12
+ def article_ids
13
+ query('SELECT Id FROM KnowledgeArticle').map(&:Id)
14
+ end
15
+
16
+ # Enumerates articles
17
+ def articles
18
+ Enumerator.new do |y|
19
+ article_ids.each do |id|
20
+ y << article(id)
21
+ end
22
+ end
23
+ end
24
+
25
+ # Gets an article by ID
26
+ def article(id)
27
+ url = index.knowledgeManagement.articles.article
28
+ url = url(url, ArticleID: id)
29
+ decorate(get(url).body) { |o| autodefine(o) }
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,52 @@
1
+ module SFKB
2
+ # Some REST helpers
3
+ module REST
4
+ # Used to interpolate variables into REST endpoint URIs
5
+ @@placeholder = /[<{](.+?)[>}]/
6
+
7
+ # { a: :b }.inject({}, &@@stringify) #=> { 'a' => :b }
8
+ @@stringify = lambda do |collector, kv|
9
+ collector.tap { |h| h[kv[0].to_s] = kv[1] }
10
+ end
11
+
12
+ # { a: 1 }.delete(:b, &@@required) # raises 'missing param: b'
13
+ @@required = -> (s) { raise(ArgumentError, "missing param: <#{s}>") }
14
+
15
+ # { a: b, c: d }.inject('x', &@@parameterize) #=> 'x?a=b&c=d'
16
+ @@parameterize = lambda do |s, kv|
17
+ k, *vs = *kv
18
+ params = vs.flatten.map { |v| [k, v].join('=') }
19
+ [s, params].flatten.compact.reject(&:empty?).join('&')
20
+ end
21
+
22
+ # Converts a path and params to a Salesforce-suitable URL.
23
+ def url(path, params = {})
24
+ params = params.inject({}, &@@stringify)
25
+ path = path.gsub(@@placeholder) { params.delete($1, &@@required) }
26
+ params = params.inject('', &@@parameterize)
27
+ [path, params].reject(&:nil?).reject(&:empty?).join('?')
28
+ end
29
+
30
+ # The REST API starts here
31
+ def index
32
+ endpoint("/services/data/v#{options[:api_version]}") do |k, v|
33
+ endpoint(v) { |k, v| endpoint(v) }
34
+ end
35
+ end
36
+
37
+ # Fetches the object at url, and extends it with methods
38
+ def endpoint(url)
39
+ get(url).body.tap do |o|
40
+ o.each do |k, v|
41
+ o.define_singleton_method(k) do
42
+ ivar = :"@#{k}"
43
+ return instance_variable_get(ivar) if instance_variable_defined?(ivar)
44
+ instance_variable_set(ivar, block_given? ? yield(k, v) : v)
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ extend self
51
+ end
52
+ end
@@ -0,0 +1,23 @@
1
+ module SFKB
2
+ # Methods for getting Salesforce Knowledge settings. Mix it into something
3
+ # with an index.
4
+ module Settings
5
+ # Tells the default language
6
+ def defaultLanguage
7
+ settings.defaultLanguage
8
+ end
9
+
10
+ # The list of languages
11
+ def languages
12
+ settings.languages
13
+ end
14
+
15
+ def active_languages
16
+ settings.languages.select(&:active).map(&:name)
17
+ end
18
+
19
+ def settings
20
+ index.knowledgeManagement.settings
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ require 'test_helper'
2
+ require 'sfkb'
3
+
4
+ describe SFKB::Client do
5
+ let(:client) { SFKB::Client.new }
6
+
7
+ it 'is a Restforce of nature' do
8
+ assert SFKB::Client < Restforce::Data::Client
9
+ end
10
+
11
+ it 'knows how to REST' do
12
+ assert SFKB::Client < SFKB::REST
13
+ end
14
+
15
+ it 'has great Knowledge' do
16
+ assert SFKB::Client < SFKB::Knowledge
17
+ end
18
+ end
@@ -0,0 +1,66 @@
1
+ require 'test_helper'
2
+ require 'sfkb/decoration'
3
+
4
+ describe SFKB::Decoration do
5
+ include SFKB::Decoration
6
+
7
+ describe 'decorate(String)' do
8
+ it 'returns the string' do
9
+ assert_equal 'a', decorate('a')
10
+ end
11
+
12
+ it 'yields the string' do
13
+ decorate('a') do |x|
14
+ assert_equal 'a', x
15
+ end
16
+ end
17
+ end
18
+
19
+ describe 'decorate({x: y})' do
20
+ it 'returns the hash' do
21
+ assert_equal({ x: :y }, decorate(x: :y))
22
+ end
23
+ end
24
+
25
+ describe 'define_link(obj, name, url)' do
26
+ it 'defines a getter method for the link' do
27
+ foo = 'hi'
28
+ define_singleton_method(:get) { |x| OpenStruct.new(body: 'Found me') }
29
+ define_link(foo, 'bar', '/baz')
30
+ assert_respond_to foo, :bar
31
+ assert_equal "Found me", foo.bar
32
+ end
33
+ end
34
+
35
+ describe 'define_predicate(obj, name, value)' do
36
+ it 'defines a predicate method for value' do
37
+ foo = 'hi'
38
+ define_predicate(foo, 'bar', true)
39
+ define_predicate(foo, 'baz', false)
40
+ assert foo.bar?
41
+ refute foo.baz?
42
+ end
43
+ end
44
+
45
+ describe 'define_links(obj, { a: "/l1", b: "/l2" })' do
46
+ it 'links all urls to those names' do
47
+ foo = 'hi'
48
+ define_links(foo, { a: '/l1', b: '/l2' })
49
+ define_singleton_method(:get) { |x| OpenStruct.new(body: "I was at #{x}!") }
50
+ assert_equal 'I was at /l1!', foo.a
51
+ assert_equal 'I was at /l2!', foo.b
52
+ end
53
+ end
54
+
55
+ describe 'autodefine' do
56
+ let(:foo) { OpenStruct.new(additionalInformation: { isTall: true, isFat: false, urls: { a: '/l1' }, data: '/l3' }) }
57
+ it 'uses additionalInformation' do
58
+ define_singleton_method(:get) { |x| OpenStruct.new(body: "I was at #{x}!") }
59
+ autodefine(foo)
60
+ assert foo.isTall?
61
+ refute foo.isFat?
62
+ assert_equal 'I was at /l1!', foo.a
63
+ assert_equal 'I was at /l3!', foo.data
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,48 @@
1
+ require 'test_helper'
2
+ require 'hipsterhash'
3
+ require 'sfkb/knowledge'
4
+
5
+ describe SFKB::Knowledge do
6
+ let(:subject) { klass.new }
7
+
8
+ let(:klass) do
9
+ Class.new(Minitest::Mock) do
10
+ include SFKB::Knowledge
11
+ define_method(:index) do
12
+ HipsterHash.new.tap do |hh|
13
+ hh[:knowledgeManagement] = {
14
+ articles: {
15
+ article: '/articles/<ArticleID>'
16
+ }
17
+ }
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ let(:ids) { %w(1 2 3).map { |id| Struct.new(:Id).new(id) } }
24
+
25
+ describe 'article_ids' do
26
+ it 'gets a list of article IDs' do
27
+ subject.expect(:query, ids, [/KnowledgeArticle/])
28
+ assert_equal %w(1 2 3), subject.article_ids
29
+ end
30
+ end
31
+
32
+ describe 'articles' do
33
+ it 'gets all articles based on the article_ids' do
34
+ subject.expect(:query, ids, [/KnowledgeArticle/])
35
+ subject.expect(:get, Struct.new(:body).new('a'), ['/articles/1'])
36
+ subject.expect(:get, Struct.new(:body).new('b'), ['/articles/2'])
37
+ subject.expect(:get, Struct.new(:body).new('c'), ['/articles/3'])
38
+ assert_equal %w(a b c), subject.articles.to_a
39
+ end
40
+ end
41
+
42
+ describe 'article' do
43
+ it 'gets a particular article' do
44
+ subject.expect(:get, Struct.new(:body).new('a'), ['/articles/1'])
45
+ assert_equal 'a', subject.article(1)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,57 @@
1
+ require 'test_helper'
2
+ require 'sfkb/rest'
3
+
4
+ describe SFKB::REST do
5
+ let(:klass) { Class.new(Minitest::Mock) { include SFKB::REST } }
6
+ let(:subject) { klass.new }
7
+ let(:response) { |x| OpenStruct.new(body: x) }
8
+
9
+ describe 'url' do
10
+ it 'joins paths to params amd substitutes vars' do
11
+ assert_equal '/foo', subject.url('/foo')
12
+ assert_equal '/foo?x=1', subject.url('/foo', x: 1)
13
+ assert_equal '/foo?x=1&y=2', subject.url('/foo', x: 1, y: 2)
14
+ end
15
+
16
+ it 'substitutes vars' do
17
+ assert_equal '/foo/1/2', subject.url('/foo/<x>/<y>', x: 1, y: 2)
18
+ assert_equal '/foo/1?y=2', subject.url('/foo/<x>', x: 1, y: 2)
19
+ assert_equal '/foo/1?b=2&b=3&c=4',
20
+ subject.url('/foo/<a>', a: 1, b: %w(2 3), c: 4)
21
+ assert_raises { subject.url('/foo/<blah>') }
22
+ end
23
+ end
24
+
25
+ describe 'index' do
26
+ let(:index) { subject.index }
27
+
28
+ before do
29
+ subject.expect(:options, { api_version: 'X' })
30
+ subject.expect(:get, OpenStruct.new(body: { a: '/a' }), ['/services/data/vX'])
31
+ end
32
+
33
+ it 'looks up the index for the current api version' do
34
+ index
35
+ subject.verify
36
+ end
37
+
38
+ it 'is a whatever was returned, hopefully a hash' do
39
+ assert_equal({ a: '/a' }, index)
40
+ end
41
+
42
+ it 'adds endpoints for the url values of the hash' do
43
+ subject.expect(:get, OpenStruct.new(body: { b: '/b', c: '/c' }), ['/a'])
44
+ assert_equal({ b: '/b', c: '/c'}, index.a)
45
+ end
46
+
47
+ it 'adds endpoints for the subresource' do
48
+ subject.expect(:get, OpenStruct.new(body: { b: '/b', c: '/c' }), ['/a'])
49
+ subject.expect(:get, OpenStruct.new(body: { d: '/d', e: '/e' }), ['/b'])
50
+ subject.expect(:get, OpenStruct.new(body: { f: '/f', g: '/g' }), ['/c'])
51
+ assert_equal '/d', index.a.b.d
52
+ assert_equal '/e', index.a.b.e
53
+ assert_equal '/f', index.a.c.f
54
+ assert_equal '/g', index.a.c.g
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,30 @@
1
+ require 'test_helper'
2
+ require 'sfkb/settings'
3
+
4
+ describe SFKB::Settings do
5
+ let(:subject) { klass.new }
6
+ let(:klass) { Class.new(Minitest::Mock) { include SFKB::Settings } }
7
+
8
+ let(:index) { Minitest::Mock.new('index') }
9
+ let(:languages) { %w(zh ja en).map { |l| OpenStruct.new(name: l, active: l != 'ja') } }
10
+ let(:knowledgeManagement) { Minitest::Mock.new('knowledgeManagement') }
11
+ let(:settings) { OpenStruct.new(defaultLanguage: 'en', languages: languages) }
12
+
13
+ before do
14
+ subject.expect :index, index
15
+ index.expect :knowledgeManagement, knowledgeManagement
16
+ knowledgeManagement.expect :settings, settings
17
+ end
18
+
19
+ it 'knows its default language' do
20
+ assert_equal 'en', subject.defaultLanguage
21
+ end
22
+
23
+ it 'knows its languages' do
24
+ assert_equal languages, subject.languages
25
+ end
26
+
27
+ it 'knows its active languages' do
28
+ assert_equal %w(zh en), subject.active_languages
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ require 'test_helper'
2
+ require 'sfkb'
3
+
4
+ describe SFKB do
5
+ let(:sfkb) { SFKB.new }
6
+
7
+ it 'constructs a client' do
8
+ assert_instance_of SFKB::Client, SFKB.new
9
+ end
10
+
11
+ it 'can list articles' do
12
+ assert_respond_to sfkb.articles, :each
13
+ end
14
+
15
+ describe 'an article' do
16
+ end
17
+ end
@@ -0,0 +1,22 @@
1
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
2
+
3
+ require 'simplecov'
4
+ SimpleCov.start
5
+
6
+ require 'dotenv/load'
7
+
8
+ require 'vcr'
9
+ VCR.configure do |vcr|
10
+ vcr.cassette_library_dir = 'test/cassettes'
11
+ vcr.hook_into :faraday
12
+ ENV.keys.grep(/^SALESFORCE_/).each do |var|
13
+ vcr.filter_sensitive_data(var) { ENV[var] }
14
+ end
15
+ end
16
+
17
+ require 'minitest-vcr'
18
+ MinitestVcr::Spec.configure!
19
+
20
+ require 'pry'
21
+
22
+ require 'minitest/autorun'
metadata ADDED
@@ -0,0 +1,286 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sfkb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - JJ Buckley
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-03-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: restforce
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: oauth2
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: hipsterhash
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.0.4
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.0.4
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.16'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.16'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '12.3'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '12.3'
83
+ - !ruby/object:Gem::Dependency
84
+ name: minitest
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '5.11'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '5.11'
97
+ - !ruby/object:Gem::Dependency
98
+ name: dotenv
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '2.2'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '2.2'
111
+ - !ruby/object:Gem::Dependency
112
+ name: faraday
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.12'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.12'
125
+ - !ruby/object:Gem::Dependency
126
+ name: vcr
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '4.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '4.0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: minitest-vcr
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '1.4'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '1.4'
153
+ - !ruby/object:Gem::Dependency
154
+ name: pry
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '0.11'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '0.11'
167
+ - !ruby/object:Gem::Dependency
168
+ name: byebug
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '10.0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '10.0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: pry-byebug
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '3.6'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '3.6'
195
+ - !ruby/object:Gem::Dependency
196
+ name: rack-test
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: '0.8'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: '0.8'
209
+ - !ruby/object:Gem::Dependency
210
+ name: simplecov
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - "~>"
214
+ - !ruby/object:Gem::Version
215
+ version: '0.15'
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - "~>"
221
+ - !ruby/object:Gem::Version
222
+ version: '0.15'
223
+ description: |
224
+ A Ruby library (based on Restforce) for working with the Salesforce Knowledge
225
+ Base.
226
+ email:
227
+ - jj@bjjb.org
228
+ executables:
229
+ - sfkb
230
+ extensions: []
231
+ extra_rdoc_files: []
232
+ files:
233
+ - LICENSE.txt
234
+ - README.md
235
+ - bin/bundle
236
+ - bin/byebug
237
+ - bin/coderay
238
+ - bin/console
239
+ - bin/dotenv
240
+ - bin/listen
241
+ - bin/pry
242
+ - bin/rackup
243
+ - bin/rake
244
+ - bin/rerun
245
+ - bin/safe_yaml
246
+ - bin/setup
247
+ - bin/sfkb
248
+ - exe/sfkb
249
+ - lib/sfkb.rb
250
+ - lib/sfkb/client.rb
251
+ - lib/sfkb/decoration.rb
252
+ - lib/sfkb/knowledge.rb
253
+ - lib/sfkb/rest.rb
254
+ - lib/sfkb/settings.rb
255
+ - test/sfkb/client_test.rb
256
+ - test/sfkb/decoration_test.rb
257
+ - test/sfkb/knowledge_test.rb
258
+ - test/sfkb/rest_test.rb
259
+ - test/sfkb/settings_test.rb
260
+ - test/sfkb_test.rb
261
+ - test/test_helper.rb
262
+ homepage: https://github.com/bjjb/sfkb
263
+ licenses:
264
+ - MIT
265
+ metadata: {}
266
+ post_install_message:
267
+ rdoc_options: []
268
+ require_paths:
269
+ - lib
270
+ required_ruby_version: !ruby/object:Gem::Requirement
271
+ requirements:
272
+ - - ">="
273
+ - !ruby/object:Gem::Version
274
+ version: '0'
275
+ required_rubygems_version: !ruby/object:Gem::Requirement
276
+ requirements:
277
+ - - ">="
278
+ - !ruby/object:Gem::Version
279
+ version: '0'
280
+ requirements: []
281
+ rubyforge_project:
282
+ rubygems_version: 2.7.3
283
+ signing_key:
284
+ specification_version: 4
285
+ summary: SalesForce Knowledge Base helper library
286
+ test_files: []