From a583f9c96f1377d7579e13a223c5fc72aa8f410c Mon Sep 17 00:00:00 2001 From: Renato Oliveira Date: Sun, 29 Nov 2020 02:31:39 -0300 Subject: [PATCH] Eliminate content and clear page. --- Pipfile | 15 -- Pipfile.lock | 107 --------- content/posts/2020-06-20-a-demo.html | 19 -- content/projects.yaml | 22 -- content/source/posts/patch_method.md | 111 --------- content/source/projects/dlrs.md | 13 -- content/source/projects/dogeforce.md | 32 --- content/source/projects/mas.md | 7 - css/site.css | 27 +-- index.html | 134 ++--------- makefile | 5 - posts/2020-12-25-patch_method.html | 219 ------------------ ...larative-rollup-summaries-for-lookups.html | 125 ---------- projects/dogeforce-s-website-this-site.html | 136 ----------- projects/mass-action-scheduler.html | 124 ---------- src/scripts/main.py | 83 ------- src/scripts/md2lds.py | 27 --- src/scripts/mdmetadata.py | 50 ---- src/templates/base.html | 34 --- src/templates/footer.html | 21 -- src/templates/header.html | 38 --- src/templates/home.html | 21 -- src/templates/post.html | 19 -- src/templates/project.html | 18 -- src/templates/repositorylist.html | 7 - 25 files changed, 23 insertions(+), 1391 deletions(-) delete mode 100644 Pipfile delete mode 100644 Pipfile.lock delete mode 100644 content/posts/2020-06-20-a-demo.html delete mode 100644 content/projects.yaml delete mode 100644 content/source/posts/patch_method.md delete mode 100644 content/source/projects/dlrs.md delete mode 100644 content/source/projects/dogeforce.md delete mode 100644 content/source/projects/mas.md delete mode 100644 makefile delete mode 100644 posts/2020-12-25-patch_method.html delete mode 100644 projects/declarative-rollup-summaries-for-lookups.html delete mode 100644 projects/dogeforce-s-website-this-site.html delete mode 100644 projects/mass-action-scheduler.html delete mode 100644 src/scripts/main.py delete mode 100644 src/scripts/md2lds.py delete mode 100644 src/scripts/mdmetadata.py delete mode 100644 src/templates/base.html delete mode 100644 src/templates/footer.html delete mode 100644 src/templates/header.html delete mode 100644 src/templates/home.html delete mode 100644 src/templates/post.html delete mode 100644 src/templates/project.html delete mode 100644 src/templates/repositorylist.html diff --git a/Pipfile b/Pipfile deleted file mode 100644 index ac1c483..0000000 --- a/Pipfile +++ /dev/null @@ -1,15 +0,0 @@ -[[source]] -name = "pypi" -url = "https://pypi.org/simple" -verify_ssl = true - -[dev-packages] - -[packages] -jinja2 = "*" -pyyaml = "*" -python-slugify = "*" -markdown = "*" - -[requires] -python_version = "3.8" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index 9e81ad3..0000000 --- a/Pipfile.lock +++ /dev/null @@ -1,107 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "20c17e4799c7c276ea4a45a2611e76cc79bc0e474a9d87b518f0b3197baa4342" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.8" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "jinja2": { - "hashes": [ - "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", - "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" - ], - "index": "pypi", - "version": "==2.11.2" - }, - "markdown": { - "hashes": [ - "sha256:1fafe3f1ecabfb514a5285fca634a53c1b32a81cb0feb154264d55bf2ff22c17", - "sha256:c467cd6233885534bf0fe96e62e3cf46cfc1605112356c4f9981512b8174de59" - ], - "index": "pypi", - "version": "==3.2.2" - }, - "markupsafe": { - "hashes": [ - "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", - "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", - "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", - "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", - "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", - "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", - "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", - "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", - "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", - "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", - "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", - "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", - "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", - "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", - "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", - "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", - "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", - "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", - "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", - "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", - "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", - "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", - "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", - "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", - "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", - "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", - "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", - "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", - "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", - "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", - "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.1.1" - }, - "python-slugify": { - "hashes": [ - "sha256:a8fc3433821140e8f409a9831d13ae5deccd0b033d4744d94b31fea141bdd84c" - ], - "index": "pypi", - "version": "==4.0.0" - }, - "pyyaml": { - "hashes": [ - "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", - "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", - "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", - "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", - "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", - "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", - "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", - "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", - "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", - "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", - "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" - ], - "index": "pypi", - "version": "==5.3.1" - }, - "text-unidecode": { - "hashes": [ - "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", - "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93" - ], - "version": "==1.3" - } - }, - "develop": {} -} diff --git a/content/posts/2020-06-20-a-demo.html b/content/posts/2020-06-20-a-demo.html deleted file mode 100644 index 15a1f5d..0000000 --- a/content/posts/2020-06-20-a-demo.html +++ /dev/null @@ -1,19 +0,0 @@ -
-

The DLRS (Declarative Lookup Rollup Summaries) tool

- -

Use cases

- - - -

Considerations

- -

The tool has gone through a big migration in the past. It used to use custom settings for managing rollups, but since Salesforce introduced Custom Metadata Types, the tool uses them.

- -

Repository

- -

The source code can be found on GitHub.

- -
\ No newline at end of file diff --git a/content/projects.yaml b/content/projects.yaml deleted file mode 100644 index c5020a5..0000000 --- a/content/projects.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# Each project should have the following format: -# -# title: Project's Title -# location: github -# url: github.com/repos/url - -projects: - - - title: Declarative Rollup Summaries for Lookups - description: dlrs.md - location: github - url: https://github.com/afawcett/declarative-lookup-rollup-summaries/ - - - title: Mass Action Scheduler - description: mas.md - location: github - url: https://github.com/douglascayers-org/sfdx-mass-action-scheduler - - - title: Dogeforce's Website (this site!) - description: dogeforce.md - location: github - url: https://github.com/Dogeforce/dogeforce.github.io/ diff --git a/content/source/posts/patch_method.md b/content/source/posts/patch_method.md deleted file mode 100644 index cb2edf4..0000000 --- a/content/source/posts/patch_method.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -title: Salesforce’s Apex does not support the PATCH method. What to do if I need to call an endpoint with PATCH? -author: @renatoliveira -publish_date: 2020-12-25 -preview: I needed to call a Microsoft Azure endpoint from Salesforce using the PATCH HTTP verb. The problem is, as mentioned in the title, that Apex does not support this verb. ---- -I had a requirement once. A proof of concept. I needed to call a Microsoft Azure endpoint from Salesforce using the PATCH HTTP verb. The problem is, as mentioned in the title, that Apex does not support this verb. - -If we are trying to call a Salesforce endpoint, there’s a trick: append `?_HttpMethod=PATCH` to the end of the URL. This is a workaround that Salesforce. This doesn’t help us because we are not calling a Salesforce endpoint. Another workaround would be setting the X-HTTP-Method-Override as PATCH in the request’s header. This is a convention that some servers follow, but this does not guarantee that the server being called will accept our request as a patch. -Let’s write a simple proxy that is hosted on Heroku! - -Leveraging a Heroku app in another cloud (technically another Salesforce cloud since 2010) we are able to forward our request to its final destination. - ->1. Salesforce calls our Heroku app endpoint ->2. The app forwards the request with the correct verb ->3. The app receives the response from Azure and forwards it back to Salesforce - -To do that, I’m going to use Python with the Flask and requests libraries. Flask will handle the “web app” part, while requests is going to be used to forward our request. - -NOTE: I am not going to cover the part where we get Azure’s access token because that doesn’t involve an unsupported verb. - -Assuming that our Salesforce code will send a request with the access token, the payload and the target URL, it will probably look like this: - - { - "token": "V2VsbCBhcmVuJ3QgeW91IGN1cmlvdXM/DQoNCiBMb3JlbSBpcHN1bSBkb2xvciBzaXQgYW1ldCwgY29uc2VjdGV0dXIgYWRpcGlzY2luZyBlbGl0LiBOdWxsYW0gcGVsbGVudGVzcXVlIHRvcnRvciBhYyBlbmltIGxhb3JlZXQsIGFjIGVsZW1lbnR1bSB0dXJwaXMgdWx0cmljaWVzLiBJbnRlZ2VyIGludGVyZHVtIHJpc3VzIGxhY3VzLCBlZ2V0IGNvbnNlcXVhdCBsaWd1bGEgZmVybWVudHVtIHZpdGFlLiBFdGlhbSBzb2RhbGVzLCBsaWJlcm8gdml0YWUgZGlnbmlzc2ltIGx1Y3R1cywgbGliZXJvIGFyY3UgdnVscHV0YXRlIHF1YW0sIGF0IG1hdHRpcyBkdWkgbWFnbmEgbmVjIG1hc3NhLiBEb25lYyBpcHN1bSBkb2xvciwgZnJpbmdpbGxhIHZpdGFlIG5pYmggYXQsIHJob25jdXMgc2NlbGVyaXNxdWUgZXN0LiBEb25lYyBuZWMgc29kYWxlcyByaXN1cy4gUGVsbGVudGVzcXVlIHF1aXMgZnJpbmdpbGxhIGVyb3MuIFBlbGxlbnRlc3F1ZSBoYWJpdGFudCBtb3JiaSB0cmlzdGlxdWUgc2VuZWN0dXMgZXQgbmV0dXMgZXQgbWFsZXN1YWRhIGZhbWVzIGFjIHR1cnBpcyBlZ2VzdGFzLiBOYW0gcnV0cnVtIG1ldHVzIG1hdXJpcywgYWMgdWxsYW1jb3JwZXIgdGVsbHVzIGF1Y3RvciBpbi4gVXQgYWNjdW1zYW4gc2NlbGVyaXNxdWUgc29kYWxlcy4gRnVzY2UgdmFyaXVzIG5lcXVlIGVzdCwgc2VkIHB1bHZpbmFyIHNlbSBzY2VsZXJpc3F1ZSBub24uIA==", - "payload": "IFNlZCB2ZW5lbmF0aXMgZXQgbWV0dXMgbm9uIGx1Y3R1cy4gUGVsbGVudGVzcXVlIGFjIGV1aXNtb2QgbWV0dXMsIG5lYyB0ZW1wb3IgZHVpLiBOYW0gYSB2ZXN0aWJ1bHVtIGZlbGlzLiBOdW5jIG1hZ25hIGxpZ3VsYSwgY29uZ3VlIG5lYyBpbXBlcmRpZXQgdXQsIGNvbmd1ZSB2dWxwdXRhdGUgcXVhbS4gTWFlY2VuYXMgYmxhbmRpdCwgZmVsaXMgbmVjIHNlbXBlciBkYXBpYnVzLCB0ZWxsdXMgaXBzdW0gdm9sdXRwYXQgYXVndWUsIGFjIGVnZXN0YXMgbmlzbCBvcmNpIG5lYyBzYXBpZW4uIEV0aWFtIGEgdnVscHV0YXRlIGVyb3MuIEN1cmFiaXR1ciBsYWNpbmlhIHNjZWxlcmlzcXVlIG5pc2wgc2VkIHZvbHV0cGF0LiBNYXVyaXMgdml0YWUgZXJhdCBwZWxsZW50ZXNxdWUsIGxhY2luaWEgdHVycGlzIHV0LCB0ZW1wb3Igc2FwaWVuLiBJbnRlZ2VyIHZlbCBsb2JvcnRpcyBkdWkuIEN1cmFiaXR1ciBpbXBlcmRpZXQgbWF0dGlzIGZlbGlzLiBQaGFzZWxsdXMgY29tbW9kbyBtYXNzYSBldSB2ZWxpdCBkYXBpYnVzIHRyaXN0aXF1ZSBhIGV1IGxpYmVyby4gRnVzY2UgaW4gcmlzdXMgZW5pbS4gRnVzY2UgZmVybWVudHVtIGV0IHB1cnVzIGV0IGNvbmRpbWVudHVtLiBJbiBzY2VsZXJpc3F1ZSBwb3N1ZXJlIGVsaXQsIHZpdGFlIGludGVyZHVtIHR1cnBpcyBjb25zZWN0ZXR1ciBhdC4g", - "url": "https://outlook.office.com/api/beta/me/contacts/31d14663-8cf4-4acf-b1c8-556b8e62107d" - } - -The app will receive this and interpret it as “okay, I’ve got this encoded payload, and I shall use this token to send it to this endpoint”: - - # Import the required libraries - # Flask is the web framework for dealing with web stuff (such as serving the app and handling - # the connections) We need to import the main "Flask" to run the app, and also its - # request and Response method and class to handle the request properly - from flask import Flask, Response, request - - # requests is a simple http request library to handle... requests. - import requests - - # Base64 is a standard module to help us encode/decode Base 64 strings - import base64 - # Json is a standar dmodule to help us handle JSON in Python (converting it from/to - # dictionaries - which are also known as maps in some other languages) - import json - # OS is a standard module to handle dealing with the OS directly (we use it just to check - # an environment variable at the end of the script) - import os - - - # Lets first create the app. This is an empty app which does nothing. - # The app will do what we want as we define the methods/routes below, with (for example) - # the `app.route` decorator (which specifies the route and allowed methods) - app = Flask(__name__) - - # This route defines that the app can receive POST requests in the `/contact/` endpoint. So - # when deployed, if the app is named `quiet-waters-12345`, its Heroku URL will be - # `https://quiet-waters-12345.herokuapp.com/` and we should hit that endpoint, adding the - # `/contact/` at the end. - @app.route('/contact/', methods=['POST']) - def contact(): - # First lets deserialize the request's JSON data into a dictionary. - request_data = request.get_json() - - # We check if there are the required attributes we need - if 'token' in request_data and 'payload' in request_data and 'url' in request_data: - try: - # We try to decode the payload - payload = base64.b64decode(request_data['payload']).decode('utf-8') - - # Assign the original payload to a new attribute named `original_payload` - # in our dictionary - request_data['original_payload'] = payload - - # Define the headers as required by the Azure endpoint - headers = { - 'Authorization': 'Bearer ' + request_data['token'], - 'Content-Type': 'application/json' - } - - # Try to call external endpoint using the requests library. Note that we - # use the `patch` method here. - azure_request = requests.patch( - url=request_data['url'], - data=payload, - headers=headers - ) - # When the request is finished, its result is stored in `azure_request`, - # which we can use to get the JSON response. - result = { - "azure_response": azure_request.json() - } - # We basically dump the request's result into a new Response and we return - # it to the service who called us in the first place. - resp = Response(json.dumps(result), status=azure_request.status_code, mimetype='applcation/json') - return resp - except Exception as e: - resp = Response(json.dumps({'error': e.args}), status=500, mimetype='applcation/json') - - # Returns an error response because there is missing data in the payload. - return Response(json.dumps({'error':'No token or payload data informed'}), status=400, mimetype='application/json') - - # Checks if the `IS_HEROKU` variable is set. If it is (in our dyno) then the app is running on - # Heroku's cloud. Otherwise it is running locally in our machine, so we want it to run in our - # localhost, on port 8080 instead (and with debug mode active). - if not os.environ.get('IS_HEROKU', None) and __name__ == '__main__': - app.run(host='localhost', port='8080', debug=True) - -And with this small web app hosted in Heroku we are not limited to a single URL. This transforms any POST request to a PATCH request. I’ve used this to call an Outlook endpoint (hence why the apps’ route was named /contacts/) but it can be renamed as needed. - -An idea would be to have all HTTP verbs available as endpoints, such as /post, /get, /delete, etc. This way the app will look more like an endpoint bus though… diff --git a/content/source/projects/dlrs.md b/content/source/projects/dlrs.md deleted file mode 100644 index ef0a637..0000000 --- a/content/source/projects/dlrs.md +++ /dev/null @@ -1,13 +0,0 @@ -## About the tool - -The DLRS is a tool that offers a feature set that extends a feature called "rollup summaries" on the Salesforce platform. It deploys Apex code to your organization and enables administrators to deploy automatically generated code to the org (even a production environment, yes). - -The tool code operates using custom metadata records managed by a Visualforce page on the project. From there an administrator or a developer can create custom metadata records for the tool to use when running its triggers (the code deployed which was mentioned before). - -When a record with a DLRS trigger is saved, it activates the DLRS tool which then gets the custom setting for the specific object. Then it queries the object and its related record(s) to update its rollup summary. - -### Example - -A rollup is created on Custom Object A (`CustomObjectA__c`) to count how many Custom Object B's (`CustomObjectB__c`) records are related to it through a common lookup field (not a master-detail relationship, that is). - -When a new object B is created, updated or deleted, the tool analyzes the criteria defined on its custom setting (the custom metadata type) and evaluates whether or not to update the counter on the parent (object A) field. diff --git a/content/source/projects/dogeforce.md b/content/source/projects/dogeforce.md deleted file mode 100644 index 68b3410..0000000 --- a/content/source/projects/dogeforce.md +++ /dev/null @@ -1,32 +0,0 @@ -## About this site - -This is an open-source website with the goal to aggregate Salesforce resources such as open source projects (like the DLRS or the Mass Action Scheduler) and comment about them, talk about their use cases, but mostly because the creator had too much free time on a weekend. - -You can check out the source code at the github repository in the link above. This website is hosted at GitHub as well. It's source is generated by a Python script that gathers the sources from many folders and "compiles" them to HTML which is then served by GitHub. The script is also custom made too (which is why it might look a little too ugly - *PR's are welcome*). - -### Contributing - -This website's source is open [on GitHub](https://github.com/Dogeforce/dogeforce.github.io/), so anyone with Python 3.8+ installed can contribute (because it is necessary to generate the files locally and push them). Just create a pull request with the content or improvements to the site's (like the CSS). The project uses [Pipenv](https://pipenv.pypa.io/en/latest/) to manage dependencies, so one can easily create a virtual Python environment to use the libraries (the four packages listed on the Pipfile). - -To write an article, create the markdown file inside the `content > source > posts` folder, and do not forget to add the metadata information on the beginning of the file, such as: - - --- - title: The title of the article - author: your @ on github or maybe twitter (or none) - publish_date: 2020-12-25 - preview: A small text explaining what the article is about - --- - -Then you can use the `make build` command to generate the necessary files. The script reads the files inside the `content > source > posts` folder and turns them into `.html` files on the `posts` folder. Alternatively, if there is no `make` command available one just needs to run the `src/scripts/main.py` file with `python` from the root folder. - -It is possible to preview the content using `make serve` to start a simple http server on the folder, so one is able to access a site version locally. - -### The Doge logo - -This amazing image was made by a member of the Salesforce Discord Exchange (SFXD). Even stickers were made of it: - -![](/images/no_logs_no_crime.jpg) - -Can you imagine being at a meeting with this guy looking at ya? - -![](/images/sticker_on_laptop.jpg) \ No newline at end of file diff --git a/content/source/projects/mas.md b/content/source/projects/mas.md deleted file mode 100644 index 333f7eb..0000000 --- a/content/source/projects/mas.md +++ /dev/null @@ -1,7 +0,0 @@ -## A quote from the project itself: - ->Mass Action Scheduler is a free-as-in-speech and open source developed passion project of Doug Ayers. - -And its goal is to give administrators the power to use Batch Apex's power, which is mostly in the hands of developers. - -With this tool an administrator can declaratively schedule a series of process automations (such as email alerts, workflow rules and flows). diff --git a/css/site.css b/css/site.css index 6802e2c..6645e14 100644 --- a/css/site.css +++ b/css/site.css @@ -1,24 +1,7 @@ -.dgf-logo { - width: 100px; -} - -.dgf-body { - min-height: 80vh; -} - -.dgf-footer { - min-height: 10vh; - position: fixed; - left: 0; - bottom: 0; - width: 100%; +html, body { + height: 100%; + margin: 0; } - -div > pre { - white-space: pre-wrap; - word-wrap: anywhere; -} - -.dgf-footer-space { - height: 10vh; +.dgf-logo { + margin-top: 20em; } \ No newline at end of file diff --git a/index.html b/index.html index 85baf5e..7ef3d69 100644 --- a/index.html +++ b/index.html @@ -1,122 +1,24 @@ - - - - - The Dogeforce Project - - - - - -
-
-
-
-

- -

-

- Dogeforce -

-
-
-
- -
- -
-
- - - - -
- -
- - - - - -
- - - - - - - -
- -
- -
-
- - -
- - - - + diff --git a/makefile b/makefile deleted file mode 100644 index 6880440..0000000 --- a/makefile +++ /dev/null @@ -1,5 +0,0 @@ -build: - python src/scripts/main.py - -serve: - python -m http.server diff --git a/posts/2020-12-25-patch_method.html b/posts/2020-12-25-patch_method.html deleted file mode 100644 index f6189c3..0000000 --- a/posts/2020-12-25-patch_method.html +++ /dev/null @@ -1,219 +0,0 @@ - - - - - - - - The Dogeforce Project - - - - - -
-
-
-
-

- -

-

- Dogeforce -

-
-
-
- -
- -
-
- - - - -
- -
- - - - - -
- - - -
-
-
Salesforce’s Apex does not support the PATCH method. What to do if I need to call an endpoint with PATCH?
-
by @renatoliveira
-
when: 2020-12-25
-
-
-

I had a requirement once. A proof of concept. I needed to call a Microsoft Azure endpoint from Salesforce using the PATCH HTTP verb. The problem is, as mentioned in the title, that Apex does not support this verb.

-

If we are trying to call a Salesforce endpoint, there’s a trick: append ?_HttpMethod=PATCH to the end of the URL. This is a workaround that Salesforce. This doesn’t help us because we are not calling a Salesforce endpoint. Another workaround would be setting the X-HTTP-Method-Override as PATCH in the request’s header. This is a convention that some servers follow, but this does not guarantee that the server being called will accept our request as a patch. -Let’s write a simple proxy that is hosted on Heroku!

-

Leveraging a Heroku app in another cloud (technically another Salesforce cloud since 2010) we are able to forward our request to its final destination.

-
-
    -
  1. Salesforce calls our Heroku app endpoint
  2. -
  3. The app forwards the request with the correct verb
  4. -
  5. The app receives the response from Azure and forwards it back to Salesforce
  6. -
-
-

To do that, I’m going to use Python with the Flask and requests libraries. Flask will handle the “web app” part, while requests is going to be used to forward our request.

-

NOTE: I am not going to cover the part where we get Azure’s access token because that doesn’t involve an unsupported verb.

-

Assuming that our Salesforce code will send a request with the access token, the payload and the target URL, it will probably look like this:

-
{
-    "token": "V2VsbCBhcmVuJ3QgeW91IGN1cmlvdXM/DQoNCiBMb3JlbSBpcHN1bSBkb2xvciBzaXQgYW1ldCwgY29uc2VjdGV0dXIgYWRpcGlzY2luZyBlbGl0LiBOdWxsYW0gcGVsbGVudGVzcXVlIHRvcnRvciBhYyBlbmltIGxhb3JlZXQsIGFjIGVsZW1lbnR1bSB0dXJwaXMgdWx0cmljaWVzLiBJbnRlZ2VyIGludGVyZHVtIHJpc3VzIGxhY3VzLCBlZ2V0IGNvbnNlcXVhdCBsaWd1bGEgZmVybWVudHVtIHZpdGFlLiBFdGlhbSBzb2RhbGVzLCBsaWJlcm8gdml0YWUgZGlnbmlzc2ltIGx1Y3R1cywgbGliZXJvIGFyY3UgdnVscHV0YXRlIHF1YW0sIGF0IG1hdHRpcyBkdWkgbWFnbmEgbmVjIG1hc3NhLiBEb25lYyBpcHN1bSBkb2xvciwgZnJpbmdpbGxhIHZpdGFlIG5pYmggYXQsIHJob25jdXMgc2NlbGVyaXNxdWUgZXN0LiBEb25lYyBuZWMgc29kYWxlcyByaXN1cy4gUGVsbGVudGVzcXVlIHF1aXMgZnJpbmdpbGxhIGVyb3MuIFBlbGxlbnRlc3F1ZSBoYWJpdGFudCBtb3JiaSB0cmlzdGlxdWUgc2VuZWN0dXMgZXQgbmV0dXMgZXQgbWFsZXN1YWRhIGZhbWVzIGFjIHR1cnBpcyBlZ2VzdGFzLiBOYW0gcnV0cnVtIG1ldHVzIG1hdXJpcywgYWMgdWxsYW1jb3JwZXIgdGVsbHVzIGF1Y3RvciBpbi4gVXQgYWNjdW1zYW4gc2NlbGVyaXNxdWUgc29kYWxlcy4gRnVzY2UgdmFyaXVzIG5lcXVlIGVzdCwgc2VkIHB1bHZpbmFyIHNlbSBzY2VsZXJpc3F1ZSBub24uIA==",
-    "payload": "IFNlZCB2ZW5lbmF0aXMgZXQgbWV0dXMgbm9uIGx1Y3R1cy4gUGVsbGVudGVzcXVlIGFjIGV1aXNtb2QgbWV0dXMsIG5lYyB0ZW1wb3IgZHVpLiBOYW0gYSB2ZXN0aWJ1bHVtIGZlbGlzLiBOdW5jIG1hZ25hIGxpZ3VsYSwgY29uZ3VlIG5lYyBpbXBlcmRpZXQgdXQsIGNvbmd1ZSB2dWxwdXRhdGUgcXVhbS4gTWFlY2VuYXMgYmxhbmRpdCwgZmVsaXMgbmVjIHNlbXBlciBkYXBpYnVzLCB0ZWxsdXMgaXBzdW0gdm9sdXRwYXQgYXVndWUsIGFjIGVnZXN0YXMgbmlzbCBvcmNpIG5lYyBzYXBpZW4uIEV0aWFtIGEgdnVscHV0YXRlIGVyb3MuIEN1cmFiaXR1ciBsYWNpbmlhIHNjZWxlcmlzcXVlIG5pc2wgc2VkIHZvbHV0cGF0LiBNYXVyaXMgdml0YWUgZXJhdCBwZWxsZW50ZXNxdWUsIGxhY2luaWEgdHVycGlzIHV0LCB0ZW1wb3Igc2FwaWVuLiBJbnRlZ2VyIHZlbCBsb2JvcnRpcyBkdWkuIEN1cmFiaXR1ciBpbXBlcmRpZXQgbWF0dGlzIGZlbGlzLiBQaGFzZWxsdXMgY29tbW9kbyBtYXNzYSBldSB2ZWxpdCBkYXBpYnVzIHRyaXN0aXF1ZSBhIGV1IGxpYmVyby4gRnVzY2UgaW4gcmlzdXMgZW5pbS4gRnVzY2UgZmVybWVudHVtIGV0IHB1cnVzIGV0IGNvbmRpbWVudHVtLiBJbiBzY2VsZXJpc3F1ZSBwb3N1ZXJlIGVsaXQsIHZpdGFlIGludGVyZHVtIHR1cnBpcyBjb25zZWN0ZXR1ciBhdC4g",
-    "url": "https://outlook.office.com/api/beta/me/contacts/31d14663-8cf4-4acf-b1c8-556b8e62107d"
-}
-
-

The app will receive this and interpret it as “okay, I’ve got this encoded payload, and I shall use this token to send it to this endpoint”:

-
# Import the required libraries
-# Flask is the web framework for dealing with web stuff (such as serving the app and handling
-# the connections) We need to import the main "Flask" to run the app, and also its
-# request and Response method and class to handle the request properly
-from flask import Flask, Response, request
-
-# requests is a simple http request library to handle... requests.
-import requests
-
-# Base64 is a standard module to help us encode/decode Base 64 strings
-import base64
-# Json is a standar dmodule to help us handle JSON in Python (converting it from/to
-# dictionaries - which are also known as maps in some other languages)
-import json
-# OS is a standard module to handle dealing with the OS directly (we use it just to check
-# an environment variable at the end of the script)
-import os
-
-
-# Lets first create the app. This is an empty app which does nothing.
-# The app will do what we want as we define the methods/routes below, with (for example)
-# the `app.route` decorator (which specifies the route and allowed methods)
-app = Flask(__name__)
-
-# This route defines that the app can receive POST requests in the `/contact/` endpoint. So
-# when deployed, if the app is named `quiet-waters-12345`, its Heroku URL will be
-# `https://quiet-waters-12345.herokuapp.com/` and we should hit that endpoint, adding the
-# `/contact/` at the end.
-@app.route('/contact/', methods=['POST'])
-def contact():
-    # First lets deserialize the request's JSON data into a dictionary.
-    request_data = request.get_json()
-
-    # We check if there are the required attributes we need
-    if 'token' in request_data and 'payload' in request_data and 'url' in request_data:
-        try:
-            # We try to decode the payload
-            payload = base64.b64decode(request_data['payload']).decode('utf-8')
-
-            # Assign the original payload to a new attribute named `original_payload`
-            # in our dictionary
-            request_data['original_payload'] = payload
-
-            # Define the headers as required by the Azure endpoint
-            headers = {
-                'Authorization': 'Bearer ' + request_data['token'],
-                'Content-Type': 'application/json'
-            }
-
-            # Try to call external endpoint using the requests library. Note that we
-            # use the `patch` method here.
-            azure_request = requests.patch(
-                url=request_data['url'],
-                data=payload,
-                headers=headers
-            )
-            # When the request is finished, its result is stored in `azure_request`,
-            # which we can use to get the JSON response.
-            result = {
-                "azure_response": azure_request.json()
-            }
-            # We basically dump the request's result into a new Response and we return
-            # it to the service who called us in the first place.
-            resp = Response(json.dumps(result), status=azure_request.status_code, mimetype='applcation/json')
-            return resp
-        except Exception as e:
-            resp = Response(json.dumps({'error': e.args}), status=500, mimetype='applcation/json')
-
-    # Returns an error response because there is missing data in the payload.
-    return Response(json.dumps({'error':'No token or payload data informed'}), status=400, mimetype='application/json')
-
-# Checks if the `IS_HEROKU` variable is set. If it is (in our dyno) then the app is running on
-# Heroku's cloud. Otherwise it is running locally in our machine, so we want it to run in our
-# localhost, on port 8080 instead (and with debug mode active).
-if not os.environ.get('IS_HEROKU', None) and __name__ == '__main__':
-    app.run(host='localhost', port='8080', debug=True)
-
-

And with this small web app hosted in Heroku we are not limited to a single URL. This transforms any POST request to a PATCH request. I’ve used this to call an Outlook endpoint (hence why the apps’ route was named /contacts/) but it can be renamed as needed.

-

An idea would be to have all HTTP verbs available as endpoints, such as /post, /get, /delete, etc. This way the app will look more like an endpoint bus though…

-
-
- - - -
- -
- -
-
- - -
- - - - - - \ No newline at end of file diff --git a/projects/declarative-rollup-summaries-for-lookups.html b/projects/declarative-rollup-summaries-for-lookups.html deleted file mode 100644 index 10f0b5f..0000000 --- a/projects/declarative-rollup-summaries-for-lookups.html +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - - The Dogeforce Project - - - - - -
-
-
-
-

- -

-

- Dogeforce -

-
-
-
- -
- -
-
- - - - -
- -
- - - - - -
- - - -
-

Declarative Rollup Summaries for Lookups

- -

Website: https://github.com/afawcett/declarative-lookup-rollup-summaries/

- -
-

About the tool

-

The DLRS is a tool that offers a feature set that extends a feature called "rollup summaries" on the Salesforce platform. It deploys Apex code to your organization and enables administrators to deploy automatically generated code to the org (even a production environment, yes).

-

The tool code operates using custom metadata records managed by a Visualforce page on the project. From there an administrator or a developer can create custom metadata records for the tool to use when running its triggers (the code deployed which was mentioned before).

-

When a record with a DLRS trigger is saved, it activates the DLRS tool which then gets the custom setting for the specific object. Then it queries the object and its related record(s) to update its rollup summary.

-

Example

-

A rollup is created on Custom Object A (CustomObjectA__c) to count how many Custom Object B's (CustomObjectB__c) records are related to it through a common lookup field (not a master-detail relationship, that is).

-

When a new object B is created, updated or deleted, the tool analyzes the criteria defined on its custom setting (the custom metadata type) and evaluates whether or not to update the counter on the parent (object A) field.

-
-
- - - -
- -
- -
-
- - -
- - - - - - \ No newline at end of file diff --git a/projects/dogeforce-s-website-this-site.html b/projects/dogeforce-s-website-this-site.html deleted file mode 100644 index 15f3f81..0000000 --- a/projects/dogeforce-s-website-this-site.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - - - The Dogeforce Project - - - - - -
-
-
-
-

- -

-

- Dogeforce -

-
-
-
- -
- -
-
- - - - -
- -
- - - - - -
- - - -
-

Dogeforce's Website (this site!)

- -

Website: https://github.com/Dogeforce/dogeforce.github.io/

- -
-

About this site

-

This is an open-source website with the goal to aggregate Salesforce resources such as open source projects (like the DLRS or the Mass Action Scheduler) and comment about them, talk about their use cases, but mostly because the creator had too much free time on a weekend.

-

You can check out the source code at the github repository in the link above. This website is hosted at GitHub as well. It's source is generated by a Python script that gathers the sources from many folders and "compiles" them to HTML which is then served by GitHub. The script is also custom made too (which is why it might look a little too ugly - PR's are welcome).

-

Contributing

-

This website's source is open on GitHub, so anyone with Python 3.8+ installed can contribute (because it is necessary to generate the files locally and push them). Just create a pull request with the content or improvements to the site's (like the CSS). The project uses Pipenv to manage dependencies, so one can easily create a virtual Python environment to use the libraries (the four packages listed on the Pipfile).

-

To write an article, create the markdown file inside the content > source > posts folder, and do not forget to add the metadata information on the beginning of the file, such as:

-
---
-title: The title of the article
-author: your @ on github or maybe twitter (or none)
-publish_date: 2020-12-25
-preview: A small text explaining what the article is about
----
-
-

The Doge logo

-

This amazing image was made by a member of the Salesforce Discord Exchange (SFXD). Even stickers were made of it:

-

-

Can you imagine being at a meeting with this guy looking at ya?

-

-
-
- - - -
- -
- -
-
- - -
- - - - - - \ No newline at end of file diff --git a/projects/mass-action-scheduler.html b/projects/mass-action-scheduler.html deleted file mode 100644 index 71bceb5..0000000 --- a/projects/mass-action-scheduler.html +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - The Dogeforce Project - - - - - -
-
-
-
-

- -

-

- Dogeforce -

-
-
-
- -
- -
-
- - - - -
- -
- - - - - -
- - - -
-

Mass Action Scheduler

- -

Website: https://github.com/douglascayers-org/sfdx-mass-action-scheduler

- -
-

A quote from the project itself:

-
-

Mass Action Scheduler is a free-as-in-speech and open source developed passion project of Doug Ayers.

-
-

And its goal is to give administrators the power to use Batch Apex's power, which is mostly in the hands of developers.

-

With this tool an administrator can declaratively schedule a series of process automations (such as email alerts, workflow rules and flows).

-
-
- - - -
- -
- -
-
- - -
- - - - - - \ No newline at end of file diff --git a/src/scripts/main.py b/src/scripts/main.py deleted file mode 100644 index 19718cb..0000000 --- a/src/scripts/main.py +++ /dev/null @@ -1,83 +0,0 @@ -import os -from datetime import datetime -from operator import attrgetter -from dataclasses import dataclass - -import yaml -from slugify import slugify -from markdown import markdown as md -from jinja2 import Environment, FileSystemLoader - -from md2lds import Md2Lds -from mdmetadata import parse_markdown - - -def slugify_url(item): - item['slug'] = slugify(item['title']) - return item - - -TODAY = datetime.today().date().isoformat() -env = Environment(loader=FileSystemLoader(searchpath='src/templates')) - -home_template = env.get_template('home.html') - -content = yaml.load(open('content/projects.yaml').read(), Loader=yaml.SafeLoader) - -content['projects'] = [slugify_url(x) for x in content['projects']] - -project_page_template = env.get_template('project.html') -post_page_template = env.get_template('post.html') -repositories = env.get_template('repositorylist.html').render(items=content['projects']) -posts = [] - - -for dire, fol, files in os.walk('content/source/posts'): - for file in files: - with open('{}/{}'.format(dire, file), 'r') as post: - post_data = parse_markdown(post) - fname = '{}-{}.html'.format(post_data.publish_date, file.replace('.md', '')) - post_data.metadata['url'] = 'posts/{}'.format(fname) - - with open('{}/{}'.format('posts', fname), 'w') as rendered_post: - print('Rendering post {} as {}...'.format(file, fname), end='') - rendered_html = md(''.join(post_data.content), extensions=[Md2Lds()]) - render = post_page_template.render( - sidebar=repositories, - content=rendered_html, - build_date=TODAY, - metadata=post_data.metadata - ) - rendered_post.write(render) - rendered_post.close() - post.close() - posts.append(post_data) - print(' OK') - -posts.sort(key=attrgetter('publish_date'), reverse=True) - -for project in content['projects']: - with open('projects/{}.html'.format(project['slug']), 'w') as page: - with open('content/source/projects/{}'.format(project['description'])) as project_md: - project['description'] = md(project_md.read(), extensions=[Md2Lds()]) - project_md.close() - - project['sidebar'] = repositories - project_page_render = project_page_template.render( - detail=project, - build_date=TODAY - ) - page.write(project_page_render) - page.close() - - -r = home_template.render( - build_date=TODAY, - page={ - 'sidebar': repositories, - 'recent_posts': posts[:5] - } -) - -with open('index.html', 'w') as index_file: - index_file.write(r) diff --git a/src/scripts/md2lds.py b/src/scripts/md2lds.py deleted file mode 100644 index 98b90d8..0000000 --- a/src/scripts/md2lds.py +++ /dev/null @@ -1,27 +0,0 @@ -from markdown.extensions import Extension -from markdown.treeprocessors import Treeprocessor - - -class LDSProcessor(Treeprocessor): - - def run(self, root): - for child in root: - self._apply(child) - - def _apply(self, el): - if el.tag == 'h1': - el.set('class', 'slds-text-heading_large slds-m-bottom_medium slds-m-top_medium') - elif el.tag == 'h2': - el.set('class', 'slds-text-heading_medium slds-m-bottom_medium slds-m-top_medium') - elif el.tag == 'h3': - el.set('class', 'slds-text-heading_small slds-m-bottom_medium slds-m-top_medium') - elif el.tag == 'p': - el.set('class', 'slds-m-bottom_medium') - elif el.tag == 'blockquote': - el.set('class', 'slds-border_left slds-p-left_small') - - - -class Md2Lds(Extension): - def extendMarkdown(self, md): - md.treeprocessors.register(LDSProcessor(), 'LDSProcessor', 100) diff --git a/src/scripts/mdmetadata.py b/src/scripts/mdmetadata.py deleted file mode 100644 index 3d5b6b6..0000000 --- a/src/scripts/mdmetadata.py +++ /dev/null @@ -1,50 +0,0 @@ -from io import TextIOWrapper -from datetime import datetime, date - - -class MarkdownWithMetadata: - metadata: dict - content: str - - def __init__(self, md, c): - self.metadata = md - self.content = c - - def __str__(self): - return '{} attributes: {}'.format(len(list(self.metadata)), ', '.join(list(self.metadata))) - - def __getattr__(self, attrname): - if attrname in self.metadata: - if 'date' in attrname and type(self.metadata[attrname]) != date: - datestrs = self.metadata[attrname].split('-')[-3:] - self.metadata[attrname] = datetime( - int(datestrs[0]), - int(datestrs[1]), - int(datestrs[2]) - ).date() - return self.metadata[attrname] - return '' - - -def parse_markdown(md: TextIOWrapper): - parsing = False - metadata = {} - lines = [] - for line in md.readlines(): - if line == '---\n' and not parsing and len(lines) == 0: - parsing = not parsing - continue - elif line == '---\n' and parsing: - parsing = not parsing - continue - - if parsing: - line_data = line.split(': ') - attr = line_data[0] - attr_data = ''.join(line_data[1:]) - metadata[attr] = attr_data.replace('\n', '') - - else: - lines.append(line) - - return MarkdownWithMetadata(metadata, ''.join(lines)) diff --git a/src/templates/base.html b/src/templates/base.html deleted file mode 100644 index 1895a69..0000000 --- a/src/templates/base.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - {% include 'header.html' %} - - -
- -
- - -
- {% block sidebar %} {% endblock %} -
- - -
- - {% block content %} {% endblock %} - -
- -
- -
-
- - -
- - -{% include 'footer.html' %} - - diff --git a/src/templates/footer.html b/src/templates/footer.html deleted file mode 100644 index b50288b..0000000 --- a/src/templates/footer.html +++ /dev/null @@ -1,21 +0,0 @@ - - \ No newline at end of file diff --git a/src/templates/header.html b/src/templates/header.html deleted file mode 100644 index 4289e55..0000000 --- a/src/templates/header.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - The Dogeforce Project - - - - - -
-
-
-
-

- -

-

- Dogeforce -

-
-
-
- -
- -
-
- - \ No newline at end of file diff --git a/src/templates/home.html b/src/templates/home.html deleted file mode 100644 index eec5176..0000000 --- a/src/templates/home.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends 'base.html' %} -{% block sidebar %} - {{ page.sidebar }} -{% endblock %} - -{% block content %} - - - -{% endblock %} diff --git a/src/templates/post.html b/src/templates/post.html deleted file mode 100644 index fa555a6..0000000 --- a/src/templates/post.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends 'base.html' %} -{% block sidebar %} - {{ sidebar }} -{% endblock %} - -{% block content %} - -
-
-
{{ metadata.title }}
-
by {{ metadata.author }}
-
when: {{ metadata.publish_date }}
-
-
- {{ content }} -
-
- -{% endblock %} diff --git a/src/templates/project.html b/src/templates/project.html deleted file mode 100644 index 77880f8..0000000 --- a/src/templates/project.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends 'base.html' %} -{% block sidebar %} - {{ detail.sidebar }} -{% endblock %} - -{% block content %} - -
-

{{ detail.title }}

- -

Website: {{ detail.url }}

- -
- {{ detail.description }} -
-
- -{% endblock %} diff --git a/src/templates/repositorylist.html b/src/templates/repositorylist.html deleted file mode 100644 index 3919dd6..0000000 --- a/src/templates/repositorylist.html +++ /dev/null @@ -1,7 +0,0 @@ - \ No newline at end of file