Merge pull request #17 from AOEpeople/next

Update to version #2 (from next branch)
This commit is contained in:
Daniel Pötzinger
2018-05-23 13:56:13 +02:00
committed by GitHub
101 changed files with 2720 additions and 1222 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
.idea
.DS_Store
dist
node_modules

View File

@@ -1,16 +1,31 @@
language: node_js
node_js: node
install: npm install
script: npm run build
install: yarn install
script: yarn build
deploy:
provider: s3
access_key_id:
secure: IqwsUtTxYBprni5jMtDFZxXzOwes6aTOKFcyEK6bN2uDjDTxbEf8Aub41I56dkzmCuxxN0233PktgTm8l3z62iK+dDbC/rwmBMBU4fVgtlehTt+wqaaJx1fyTytm2sgIXV5rsRCH+XUY5dfPEkv1FLYIDbw+YJ8Fm1vIEWQ7x1ROi1b59GuYwUSOq1ZQEH4lEfcsSJu4wLKuRjhGagHcucZ77Z+DBvwktwqe1g1L5S4TmFXCSaYV7ceYu9zRtzIiyN9AK6HAKXqptKJK541oYRaCOo3fPPkShKCYqFZDgUCd8ele4G1JaJObii7NznkpBsaZjhT1Yt1risf0hGZFSL9BPKRbBuvBpvoJx7t41ppo5CUot37bontiJ/tDJ1QKl34P5yqPatFKQnM6nlR3K+IW0EdNL1XcYLfKYkxh1loUasy5Zf26vC7j+Q9K2jubidd3YcqKWcC0VAttvB6hw60hQ3XdE9EcH5OzvhDg1Ju5zmq4VrHDL8ymHgRmZzQPtdpon5S0j9Z45YkTPqc2XGWJTuq1ED7Hq3wO/6ry4+roAeAmZ1qXLww+LWcm/Yki9/y++e19+JxhSFNWEZK4kn7K7TquumSF6gP7ukJj8TgG4kXRZs7vcl0J4nYJA/qYQFMbVfw8SgdXT/zLNQaK6RNH3x3uOMfl3VDt3sAY2rk=
secret_access_key:
secure: f8ThXSrY6huRhUteqgcFm1e21DPOFcSlNq6SzmHEPbXn9XkzyQthjtZk5W1xboww+TJCs8um+mZ7liebBntF6YyS/2I/uA/XX957K6qpW9x3zOvqoEkhurJ8pJQA6HP0lJP2lwiDvIgytd1VafzPw1iqQjQVqblpuGodzFR/i17pISdj1NJD5HP6wNHCAukjPiYAhXDxuxdCb042fYtXE75MiidIwOcfBjmShAO1fOBxtHq9nyUg7esbMJavx6QAtXVxBuqOAPCU8rtJH7oq4b8FNuH1QtByAUqIG3dgXMdCltKRt0FP6+c5gpO5rWaGD0XkVPxhCo/XJcAbpNKdYXBRQtp5Ki2PAnnOJVJ5fp6raXNI5tereyIzYQPkoOrLruB9tWtCiFd0qHTuiqe4xVfTQK7EGxe99cKl1axxFDVeLayNOrsy9J1zob7i/HmGsgh2UT32+ao2TOn0IKx/5y7njyXpaBTokIk8qJlb0wIuT2R5LHfzKFK2EoI+v0EWWiZXHqSpUYpPMCF6QUNIotWm+61M8yHx9Z2/XzllZXEFA+ZAFOSTeNU8xCbZ3hhdIN6x8XQ23Qi1xVVfE1DT8ZrTmCzLPoQwvxnzR4wN157tbHfYFOiLc2cORv7HptdeipQhdUXBAD+7C5aGQxbu4pi7GNy0vKiaaNSG8RdZjIg=
bucket: techradar.aoe.com
skip_cleanup: true
acl: public_read
region: eu-central-1
local_dir: dist
detect_encoding: true
- provider: s3
access_key_id:
secure: IqwsUtTxYBprni5jMtDFZxXzOwes6aTOKFcyEK6bN2uDjDTxbEf8Aub41I56dkzmCuxxN0233PktgTm8l3z62iK+dDbC/rwmBMBU4fVgtlehTt+wqaaJx1fyTytm2sgIXV5rsRCH+XUY5dfPEkv1FLYIDbw+YJ8Fm1vIEWQ7x1ROi1b59GuYwUSOq1ZQEH4lEfcsSJu4wLKuRjhGagHcucZ77Z+DBvwktwqe1g1L5S4TmFXCSaYV7ceYu9zRtzIiyN9AK6HAKXqptKJK541oYRaCOo3fPPkShKCYqFZDgUCd8ele4G1JaJObii7NznkpBsaZjhT1Yt1risf0hGZFSL9BPKRbBuvBpvoJx7t41ppo5CUot37bontiJ/tDJ1QKl34P5yqPatFKQnM6nlR3K+IW0EdNL1XcYLfKYkxh1loUasy5Zf26vC7j+Q9K2jubidd3YcqKWcC0VAttvB6hw60hQ3XdE9EcH5OzvhDg1Ju5zmq4VrHDL8ymHgRmZzQPtdpon5S0j9Z45YkTPqc2XGWJTuq1ED7Hq3wO/6ry4+roAeAmZ1qXLww+LWcm/Yki9/y++e19+JxhSFNWEZK4kn7K7TquumSF6gP7ukJj8TgG4kXRZs7vcl0J4nYJA/qYQFMbVfw8SgdXT/zLNQaK6RNH3x3uOMfl3VDt3sAY2rk=
secret_access_key:
secure: f8ThXSrY6huRhUteqgcFm1e21DPOFcSlNq6SzmHEPbXn9XkzyQthjtZk5W1xboww+TJCs8um+mZ7liebBntF6YyS/2I/uA/XX957K6qpW9x3zOvqoEkhurJ8pJQA6HP0lJP2lwiDvIgytd1VafzPw1iqQjQVqblpuGodzFR/i17pISdj1NJD5HP6wNHCAukjPiYAhXDxuxdCb042fYtXE75MiidIwOcfBjmShAO1fOBxtHq9nyUg7esbMJavx6QAtXVxBuqOAPCU8rtJH7oq4b8FNuH1QtByAUqIG3dgXMdCltKRt0FP6+c5gpO5rWaGD0XkVPxhCo/XJcAbpNKdYXBRQtp5Ki2PAnnOJVJ5fp6raXNI5tereyIzYQPkoOrLruB9tWtCiFd0qHTuiqe4xVfTQK7EGxe99cKl1axxFDVeLayNOrsy9J1zob7i/HmGsgh2UT32+ao2TOn0IKx/5y7njyXpaBTokIk8qJlb0wIuT2R5LHfzKFK2EoI+v0EWWiZXHqSpUYpPMCF6QUNIotWm+61M8yHx9Z2/XzllZXEFA+ZAFOSTeNU8xCbZ3hhdIN6x8XQ23Qi1xVVfE1DT8ZrTmCzLPoQwvxnzR4wN157tbHfYFOiLc2cORv7HptdeipQhdUXBAD+7C5aGQxbu4pi7GNy0vKiaaNSG8RdZjIg=
bucket: techradar.aoe.com
skip_cleanup: true
acl: public_read
region: eu-central-1
local_dir: dist
detect_encoding: true
on:
branch: master
- provider: s3
access_key_id:
secure: IqwsUtTxYBprni5jMtDFZxXzOwes6aTOKFcyEK6bN2uDjDTxbEf8Aub41I56dkzmCuxxN0233PktgTm8l3z62iK+dDbC/rwmBMBU4fVgtlehTt+wqaaJx1fyTytm2sgIXV5rsRCH+XUY5dfPEkv1FLYIDbw+YJ8Fm1vIEWQ7x1ROi1b59GuYwUSOq1ZQEH4lEfcsSJu4wLKuRjhGagHcucZ77Z+DBvwktwqe1g1L5S4TmFXCSaYV7ceYu9zRtzIiyN9AK6HAKXqptKJK541oYRaCOo3fPPkShKCYqFZDgUCd8ele4G1JaJObii7NznkpBsaZjhT1Yt1risf0hGZFSL9BPKRbBuvBpvoJx7t41ppo5CUot37bontiJ/tDJ1QKl34P5yqPatFKQnM6nlR3K+IW0EdNL1XcYLfKYkxh1loUasy5Zf26vC7j+Q9K2jubidd3YcqKWcC0VAttvB6hw60hQ3XdE9EcH5OzvhDg1Ju5zmq4VrHDL8ymHgRmZzQPtdpon5S0j9Z45YkTPqc2XGWJTuq1ED7Hq3wO/6ry4+roAeAmZ1qXLww+LWcm/Yki9/y++e19+JxhSFNWEZK4kn7K7TquumSF6gP7ukJj8TgG4kXRZs7vcl0J4nYJA/qYQFMbVfw8SgdXT/zLNQaK6RNH3x3uOMfl3VDt3sAY2rk=
secret_access_key:
secure: f8ThXSrY6huRhUteqgcFm1e21DPOFcSlNq6SzmHEPbXn9XkzyQthjtZk5W1xboww+TJCs8um+mZ7liebBntF6YyS/2I/uA/XX957K6qpW9x3zOvqoEkhurJ8pJQA6HP0lJP2lwiDvIgytd1VafzPw1iqQjQVqblpuGodzFR/i17pISdj1NJD5HP6wNHCAukjPiYAhXDxuxdCb042fYtXE75MiidIwOcfBjmShAO1fOBxtHq9nyUg7esbMJavx6QAtXVxBuqOAPCU8rtJH7oq4b8FNuH1QtByAUqIG3dgXMdCltKRt0FP6+c5gpO5rWaGD0XkVPxhCo/XJcAbpNKdYXBRQtp5Ki2PAnnOJVJ5fp6raXNI5tereyIzYQPkoOrLruB9tWtCiFd0qHTuiqe4xVfTQK7EGxe99cKl1axxFDVeLayNOrsy9J1zob7i/HmGsgh2UT32+ao2TOn0IKx/5y7njyXpaBTokIk8qJlb0wIuT2R5LHfzKFK2EoI+v0EWWiZXHqSpUYpPMCF6QUNIotWm+61M8yHx9Z2/XzllZXEFA+ZAFOSTeNU8xCbZ3hhdIN6x8XQ23Qi1xVVfE1DT8ZrTmCzLPoQwvxnzR4wN157tbHfYFOiLc2cORv7HptdeipQhdUXBAD+7C5aGQxbu4pi7GNy0vKiaaNSG8RdZjIg=
bucket: techradar-next.aoe.com
skip_cleanup: true
acl: public_read
region: eu-central-1
local_dir: dist
detect_encoding: true
on:
branch: next

202
LICENSE.txt Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2017 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -7,54 +7,72 @@ A static site generator for AOE Technology Radar
```
> git clone git@github.com:AOEpeople/aoe_technology_radar.git
> cd aoe_technology_radar
> npm install
> npm run watch
> yarn install
> yarn watch
```
*A new browser tab should open up - wait until last command has finished and refresh.*
_A new [browser tab](http://127.0.0.1:8080/techradar) should open up - wait
until last command has finished and refresh._
## Usage
For a new Technology Radar release, create a folder of the release date (YYYY-MM-DD) under `/radar`. In each release folder create a folder for every quadrant and place the items there.
For a new Technology Radar release, create a folder of the release date
(YYYY-MM-DD) under `/radar`. In each release folder create a folder for every
quadrant and place the items there.
### Maintaining items
The items are written in Markdown format (.md)
Each file has a [front-matter](https://github.com/jxson/front-matter) header where the attributes of the item are listed:
```
---
title: "React"
ring: adopt
quadrant: languages-and-frameworks
---
Each file has a [front-matter](https://github.com/jxson/front-matter) header
where the attributes of the item are listed:
Text goes here. You can use **markdown** here.
```
```
---
title: "React"
ring: adopt
quadrant: languages-and-frameworks
---
Text goes here. You can use **markdown** here.
```
Following front-matter attributes are possible:
- **title**: Name of the Item
- **quadrant**: Quadrant. One of `languages-and-frameworks`, `methods-and-patterns`, `platforms-and-aoe-services`, `tools`
- **ring**: Ring section in radar. One of `trial`, `assess`, `adopt`, `hold`
- **info**: (optional) A short textual description of the item (visible in overview pages)
The name of the .md file acts as item identifier and may overwrite items with the same name from older releases.
* **title**: Name of the Item
* **quadrant**: Quadrant. One of `languages-and-frameworks`,
`methods-and-patterns`, `platforms-and-aoe-services`, `tools`
* **ring**: Ring section in radar. One of `trial`, `assess`, `adopt`, `hold`
* **info**: (optional) A short textual description of the item (visible in
overview pages)
* **featured**: (optional, default "true") If you set this to `false`, the item
will not be visible in the radar quadrants but still be available in the overview.
If an item is overwritten in a new release, the attributes from the new item are merged with the old ones and a new history entry is created for that item.
The name of the .md file acts as item identifier and may overwrite items with
the same name from older releases.
If an item is overwritten in a new release, the attributes from the new item are
merged with the old ones and a new history entry is created for that item.
## Travis
[![Travis](https://api.travis-ci.org/AOEpeople/aoe_technology_radar.svg?branch=master)](https://travis-ci.org/AOEpeople/aoe_technology_radar/)
* [![Travis](https://api.travis-ci.org/AOEpeople/aoe_technology_radar.svg?branch=master)](https://travis-ci.org/AOEpeople/aoe_technology_radar/)
(master)
* [![Travis](https://api.travis-ci.org/AOEpeople/aoe_technology_radar.svg?branch=next)](https://travis-ci.org/AOEpeople/aoe_technology_radar/)
(next)
## Todos
- [x] Add Icons
- [x] Implement search
- [ ] Finalize CSS
- [x] Get contents for how-to-use page
- [ ] Show item history on details (relevant with 2nd release)
- [ ] Better semantic and SEO
- [ ] Make mobile friendly
- [ ] Add mobile navigation and search
- [ ] Create more react components for already existing CSS comps
- [ ] Refactor hardcoded subfolder in routing
- [ ] Unit test for radar generation :see_no_evil:
* [x] Add Icons
* [x] Implement search
* [ ] Finalize CSS
* [x] Get contents for how-to-use page
* [ ] Show item history on details (relevant with 2nd release)
* [ ] Better semantic and SEO
* [ ] Make mobile friendly
* [ ] Add mobile navigation and search
* [ ] Create more react components for already existing CSS comps
* [ ] Refactor hardcoded subfolder in routing
* [ ] Unit test for radar generation :see_no_evil:

View File

@@ -1,43 +1,50 @@
export const featuredOnly = items => items.filter(item => item.featured);
export const groupByQuadrants = (items) => (
items.reduce((quadrants, item) => ({
...quadrants,
[item.quadrant]: addItemToQuadrant(quadrants[item.quadrant], item),
}), {})
);
export const groupByQuadrants = items =>
items.reduce(
(quadrants, item) => ({
...quadrants,
[item.quadrant]: addItemToQuadrant(quadrants[item.quadrant], item),
}),
{},
);
export const groupByRing = (items) => (
items.reduce((rings, item) => ({
...rings,
[item.ring]: addItemToList(rings[item.ring], item),
}), {})
);
export const groupByRing = items =>
items.reduce(
(rings, item) => ({
...rings,
[item.ring]: addItemToList(rings[item.ring], item),
}),
{},
);
export const groupByFirstLetter = (items) => {
const index = items.reduce((letterIndex, item) => ({
...letterIndex,
[getFirstLetter(item)]: addItemToList(letterIndex[getFirstLetter(item)], item),
}), {});
export const groupByFirstLetter = items => {
const index = items.reduce(
(letterIndex, item) => ({
...letterIndex,
[getFirstLetter(item)]: addItemToList(
letterIndex[getFirstLetter(item)],
item,
),
}),
{},
);
return Object.keys(index).sort().map((letter) => ({
letter,
items: index[letter],
}));
}
return Object.keys(index)
.sort()
.map(letter => ({
letter,
items: index[letter],
}));
};
const addItemToQuadrant = (quadrant = {}, item) => ({
...quadrant,
[item.ring]: addItemToRing(quadrant[item.ring], item),
});
const addItemToList = (list = [], item) => ([
...list,
item,
]);
const addItemToList = (list = [], item) => [...list, item];
const addItemToRing = (ring = [], item) => ([
...ring,
item,
]);
const addItemToRing = (ring = [], item) => [...ring, item];
export const getFirstLetter = (item) => item.title.substr(0,1).toUpperCase();
export const getFirstLetter = item => item.title.substr(0, 1).toUpperCase();

View File

@@ -3,21 +3,18 @@ import path from 'path';
import frontmatter from 'front-matter';
import marked from 'marked';
import hljs from 'highlight.js';
import {
radarPath,
getAllMarkdownFiles,
} from './file';
import { radarPath, getAllMarkdownFiles } from './file';
marked.setOptions({
highlight: (code) => hljs.highlightAuto(code).value,
highlight: code => hljs.highlightAuto(code).value,
});
export const createRadar = async (tree) => {
const fileNames = (await getAllMarkdownFiles(radarPath()));
export const createRadar = async tree => {
const fileNames = await getAllMarkdownFiles(radarPath());
const revisions = await createRevisionsFromFiles(fileNames);
const allReleases = getAllReleases(revisions);
const items = createItems(revisions);
const flaggedItems = flagWithIsNew(items, allReleases);
const flaggedItems = flagItem(items, allReleases);
return {
items: flaggedItems,
@@ -28,83 +25,84 @@ export const createRadar = async (tree) => {
const checkAttributes = (fileName, attributes) => {
const rings = ['adopt', 'trial', 'assess', 'hold'];
if (attributes.ring && !rings.includes(attributes.ring)) {
throw new Error(`Error: ${fileName} has an illegal value for 'ring' - must be one of ${rings}`);
throw new Error(
`Error: ${fileName} has an illegal value for 'ring' - must be one of ${
rings
}`,
);
}
const quadrants = ['languages-and-frameworks', 'methods-and-patterns', 'platforms-and-aoe-services', 'tools'];
const quadrants = [
'languages-and-frameworks',
'methods-and-patterns',
'platforms-and-aoe-services',
'tools',
];
if (attributes.quadrant && !quadrants.includes(attributes.quadrant)) {
throw new Error(`Error: ${fileName} has an illegal value for 'quadrant' - must be one of ${quadrants}`);
throw new Error(
`Error: ${
fileName
} has an illegal value for 'quadrant' - must be one of ${quadrants}`,
);
}
};
const createRevisionsFromFiles = (fileNames) => (
Promise.all(fileNames.map((fileName) => {
return new Promise((resolve, reject) => {
readFile(fileName, 'utf8', (err, data) => {
if(err) {
reject(err);
} else {
const fm = frontmatter(data);
// prepend subfolder to links
fm.body = fm.body.replace(/\]\(\//g, '](/techradar/')
const createRevisionsFromFiles = fileNames =>
Promise.all(
fileNames.map(fileName => {
return new Promise((resolve, reject) => {
readFile(fileName, 'utf8', (err, data) => {
if (err) {
reject(err);
} else {
const fm = frontmatter(data);
// prepend subfolder to links
fm.body = fm.body.replace(/\]\(\//g, '](/techradar/');
// add target attribute to external links
let html = marked(fm.body);
html = html.replace(/a href="http/g, 'a target="_blank" href="http')
// add target attribute to external links
let html = marked(fm.body);
html = html.replace(
/a href="http/g,
'a target="_blank" href="http',
);
checkAttributes(fileName, fm.attributes);
resolve({
...itemInfoFromFilename(fileName),
...fm.attributes,
fileName,
body: html
});
}
checkAttributes(fileName, fm.attributes);
resolve({
...itemInfoFromFilename(fileName),
...fm.attributes,
fileName,
body: html,
});
}
});
});
})
}))
);
}),
);
const itemInfoFromFilename = (fileName) => {
const [
release,
nameWithSuffix,
] = fileName.split(path.sep).slice(-2);
const itemInfoFromFilename = fileName => {
const [release, nameWithSuffix] = fileName.split(path.sep).slice(-2);
return {
name: nameWithSuffix.substr(0, nameWithSuffix.length - 3),
release,
}
};
};
const getAllReleases = (revisions) => (
revisions.reduce((allReleases, { release }) => {
if(!allReleases.includes(release)) {
return [...allReleases, release];
}
return allReleases;
}, []).sort()
);
// const createQuadrants = (revisions) => (
// revisions.reduce((quadrants, revision) => {
// return {
// ...quadrants,
// [revision.quadrant]: addRevisionToQuadrant(quadrants[revision.quadrant], revision),
// };
// }, {})
// );
// const addRevisionToQuadrant = (quadrant = {}, revision) => ({
// ...quadrant,
// [revision.name]: addRevisionToItem(quadrant[revision.name], revision),
// });
const getAllReleases = revisions =>
revisions
.reduce((allReleases, { release }) => {
if (!allReleases.includes(release)) {
return [...allReleases, release];
}
return allReleases;
}, [])
.sort();
const addRevisionToQuadrant = (quadrant = {}, revision) => ({
...quadrant,
[revision.ring]: addRevisionToRing(quadrant[revision.ring], revision),
});
const createItems = (revisions) => {
const createItems = revisions => {
const itemMap = revisions.reduce((items, revision) => {
return {
...items,
@@ -112,24 +110,29 @@ const createItems = (revisions) => {
};
}, {});
return Object
.values(itemMap)
.sort((x, y) => (x.name > y.name ? 1 : -1));
return Object.values(itemMap).sort((x, y) => (x.name > y.name ? 1 : -1));
};
const addRevisionToItem = (item = {
attributes: {
isFeatured: true,
const ignoreEmptyRevisionBody = (revision, item) => {
if (!revision.body || revision.body.trim() === '') {
return item.body;
}
return revision.body;
};
const addRevisionToItem = (
item = {
flag: 'default',
featured: true,
revisions: [],
},
revisions: [],
}, revision) => {
const {
fileName,
...rest,
} = revision;
revision,
) => {
const { fileName, ...rest } = revision;
let newItem = {
...item,
...rest,
body: ignoreEmptyRevisionBody(rest, item),
attributes: {
...item.attributes,
...revision.attributes,
@@ -139,28 +142,41 @@ const addRevisionToItem = (item = {
if (revisionCreatesNewHistoryEntry(revision)) {
newItem = {
...newItem,
revisions: [
rest,
...newItem.revisions,
],
}
revisions: [rest, ...newItem.revisions],
};
}
return newItem;
};
const revisionCreatesNewHistoryEntry = (revision) => {
return revision.body.trim() !== '' ||
typeof revision.ring !== 'undefined';
const revisionCreatesNewHistoryEntry = revision => {
return revision.body.trim() !== '' || typeof revision.ring !== 'undefined';
};
const flagWithIsNew = (items, allReleases) => (
items.map((item) => ({
...item,
isNew: isNewItem(item, allReleases),
}), [])
);
const flagItem = (items, allReleases) =>
items.map(
item => ({
...item,
flag: getItemFlag(item, allReleases),
}),
[],
);
const isNewItem = (item, allReleases) => {
return item.revisions.length > 1 && item.revisions[0].release === allReleases[allReleases.length-1]
const isInLastRelease = (item, allReleases) =>
item.revisions[0].release === allReleases[allReleases.length - 1];
const isNewItem = (item, allReleases) =>
item.revisions.length === 1 && isInLastRelease(item, allReleases);
const hasItemChanged = (item, allReleases) =>
item.revisions.length > 1 && isInLastRelease(item, allReleases);
const getItemFlag = (item, allReleases) => {
if (isNewItem(item, allReleases)) {
return 'new';
}
if (hasItemChanged(item, allReleases)) {
return 'changed';
}
return 'default';
};

View File

@@ -1,17 +1,17 @@
import 'babel-polyfill';
import React from 'react';
import { render } from 'react-dom';
import { hydrate } from 'react-dom';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import createHistory from 'history/createBrowserHistory';
import App from './components/App';
import appReducer from './reducer';
import actions, { NAVIGATE } from './actions';
import {isMobileViewport} from '../common/config'
import {track} from './analytics';
import { isMobileViewport } from '../common/config';
import { track } from './analytics';
// Remove .html and map / to index
const getPageNameFromPath = (path) => {
const getPageNameFromPath = path => {
if (path === '/') {
return 'index';
}
@@ -20,29 +20,29 @@ const getPageNameFromPath = (path) => {
const historyManager = store => {
const history = createHistory({
basename: '/techradar'
basename: '/techradar',
});
// If browser-back button is pressed, provide new pageName to store
history.listen((location, action) => {
if (action === 'POP') {
const pageName = getPageNameFromPath(location.pathname);
store.dispatch(actions.navigate(pageName, false))
store.dispatch(actions.navigate(pageName, false));
}
});
return next => action => {
if(action.type === NAVIGATE && action.pushToHistory === true) {
if (action.type === NAVIGATE && action.pushToHistory === true) {
window.scrollTo(0, 0);
history.push(`/${action.pageName}.html`);
}
return next(action);
}
};
};
let reloadTimeout;
let wasMobileViewport = isMobileViewport();
window.addEventListener('resize', function () {
window.addEventListener('resize', function() {
clearTimeout(reloadTimeout);
reloadTimeout = setTimeout(() => {
if (wasMobileViewport != isMobileViewport()) window.location.reload();
@@ -56,16 +56,30 @@ const preloadedState = window.__TECHRADAR__;
delete window.__TECHRADAR__;
// Create Redux store with initial state
const store = createStore(appReducer, preloadedState, applyMiddleware(historyManager))
const store = createStore(
appReducer,
preloadedState,
applyMiddleware(historyManager),
);
const handleSetTitle = (title) => {
const handleSetTitle = title => {
document.title = `${title} | AOE Technology Radar`;
track();
};
render(
// FIXME!
// This is a quiet ugly hack, that is needed to hydrate the react root correctly on mobile:
// The main issue is, that we render different components based on the view port - which means that
// the server-generated DOM differs from the DOM that would be rendered on mobile devices.
// We therefore completely ignore the pre-rendered DOM on mobile and let the client render.
// To solve this root of this issue, we need to remove the conditional renderings that check "isMobileViewport()"!
if (isMobileViewport()) {
document.getElementById('root').innerHTML = '';
}
hydrate(
<Provider store={store}>
<App onSetTitle={handleSetTitle} />
</Provider>,
document.getElementById('root')
)
document.getElementById('root'),
);

View File

@@ -14,7 +14,9 @@ function App(props) {
<div className="page__header">
<Header {...props} />
</div>
<div className={classNames('page__content', { 'is-faded': props.isFaded })}>
<div
className={classNames('page__content', { 'is-faded': props.isFaded })}
>
<Router {...props} />
</div>
<div className="page__footer">
@@ -22,10 +24,15 @@ function App(props) {
</div>
</div>
</div>
)
);
}
export default connect(
({ items, releases, pageName, pageState }) => ({ items, releases, pageName, pageState }),
actions
({ items, releases, pageName, pageState }) => ({
items,
releases,
pageName,
pageState,
}),
actions,
)(App);

20
js/components/Flag.js Normal file
View File

@@ -0,0 +1,20 @@
import React from 'react';
function ucFirst(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
export default function Flag({ item, short = false }) {
if (item.flag !== 'default') {
let name = item.flag.toUpperCase();
let title = ucFirst(item.flag);
if (short === true) {
name = {
new: 'N',
changed: 'C',
}[item.flag];
}
return <span className={`flag flag--${item.flag}`} title={title}>{name}</span>;
}
return null;
}

View File

@@ -14,6 +14,7 @@ export default function FooterEnd({modifier}) {
<a className="social-links-icon" href="https://www.linkedin.com/company/aoe" target="_blank"><i className="socicon-linkedin social-icon"></i></a>
<a className="social-links-icon" href="https://www.xing.com/company/aoe" target="_blank"><i className="socicon-xing social-icon"></i></a>
<a className="social-links-icon" href="https://www.youtube.com/user/aoepeople" target="_blank"><i className="socicon-youtube social-icon"></i></a>
<a className="social-links-icon" href="https://github.com/aoepeople" target="_blank"><i className="socicon-github social-icon"></i></a>
</div>
</div>
<div className="footer-copyright">

View File

@@ -1,8 +1,10 @@
import React from 'react';
import classNames from 'classnames';
export default function({ children }) {
export default function({ children, secondary = false }) {
return (
<div className="headline-group">
<div
className={classNames('headline-group', {'headline-group--secondary': secondary})}>
{children}
</div>
);

View File

@@ -1,8 +1,14 @@
import React from 'react';
import classNames from 'classnames';
import Link from './Link';
import Flag from './Flag';
export default function Item({ item, noLeadingBorder = false, active = false, style = {}}) {
export default function Item({
item,
noLeadingBorder = false,
active = false,
style = {},
}) {
return (
<Link
className={classNames('item', {
@@ -12,12 +18,11 @@ export default function Item({ item, noLeadingBorder = false, active = false, st
pageName={`${item.quadrant}/${item.name}`}
style={style}
>
<div className="item__title">{item.title}</div>
{
item.info && (
<div className="item__info">{item.info}</div>
)
}
<div className="item__title">
{item.title}
<Flag item={item} />
</div>
{item.info && <div className="item__info">{item.info}</div>}
</Link>
);
}

View File

@@ -0,0 +1,14 @@
import React from 'react';
import Badge from './Badge';
import { formatRelease } from '../date';
export default function ItemRevision({ revision }) {
return (
<div className="item-revision">
<div>
<Badge type={revision.ring}>{revision.ring} | {formatRelease(revision.release)}</Badge>
</div>
<div className="markdown" dangerouslySetInnerHTML={{__html: revision.body}} />
</div>
);
}

View File

@@ -0,0 +1,17 @@
import React from 'react';
import HeadlineGroup from './HeadlineGroup';
import ItemRevision from './ItemRevision';
export default function ItemRevisions({ revisions }) {
return (
<div className="item-revisions">
<HeadlineGroup secondary>
<h4 className="headline headline--dark">Revisions:</h4>
</HeadlineGroup>
{revisions.map(revision => (
<ItemRevision key={revision.release} revision={revision} />
))}
</div>
);
}

View File

@@ -40,7 +40,7 @@ export default function PageHelp({ leaving, onLeave, ...props }) {
<li><strong>Hold:</strong> This category is a bit special. Unlike the others, we recommend to stop doing or using something. That does not mean that there are bad and it often might be ok to use them in existing projects. But we move things here if we think we shouldn't do them anymore - because we see better options or alternatives now.</li>
</ul>
<p>We also maintain a short list of useful tools - that are not worth putting on the radar - but that can belong in everyone's "toolbox". We call the list of these technologies the AOE Toolbox and you can find information about them here: <a href="aoe-toolbox.html">AOE Toolbox</a></p>
<p>Contributions and source code of the radar are on github: <a href="https://github.com/AOEpeople/aoe_technology_radar" target="_blank">AOE Tech Rdar on Github</a></p>
<p>Contributions and source code of the radar are on github: <a href="https://github.com/AOEpeople/aoe_technology_radar" target="_blank">AOE Tech Radar on Github</a></p>
</div>

View File

@@ -1,17 +1,33 @@
import React from 'react';
import { formatRelease } from '../date';
import { featuredOnly } from '../../common/model';
import HeroHeadline from './HeroHeadline';
import QuadrantGrid from './QuadrantGrid';
import Fadeable from './Fadeable';
import SetTitle from './SetTitle';
export default function PageIndex({ leaving, onLeave, items, navigate, ...props }) {
export default function PageIndex({
leaving,
onLeave,
items,
navigate,
...props
}) {
const newestRelease = props.releases.slice(-1)[0];
const numberOfReleases = props.releases.length;
return (
<Fadeable leaving={leaving} onLeave={onLeave}>
<SetTitle {...props} title="Technology Radar" />
<div className="headline-group">
<HeroHeadline alt="March 2017">AOE Technology Radar</HeroHeadline>
<HeroHeadline alt={`Version #${numberOfReleases}`}>
AOE Technology Radar
</HeroHeadline>
</div>
<QuadrantGrid items={featuredOnly(items)} />
<div className="publish-date">
Published {formatRelease(newestRelease)}
</div>
<QuadrantGrid items={items} />
</Fadeable>
);
}

View File

@@ -4,6 +4,7 @@ import ItemList from './ItemList';
import Link from './Link';
import FooterEnd from './FooterEnd';
import SetTitle from './SetTitle';
import ItemRevisions from './ItemRevisions';
import { createAnimation, createAnimationRunner } from '../animation';
import { translate } from '../../common/config';
@@ -21,55 +22,67 @@ class PageItem extends React.Component {
const itemsInRing = this.getItemsInRing(props);
this.animationsIn = {
background: createAnimation({
background: createAnimation(
{
transform: 'translateX(calc((100vw - 1200px) / 2 + 800px))',
transition: 'transform 450ms cubic-bezier(0.24, 1.12, 0.71, 0.98)',
}, {
},
{
transition: 'transform 450ms cubic-bezier(0.24, 1.12, 0.71, 0.98)',
transform: 'translateX(0)',
},
0
0,
),
navHeader: createAnimation({
navHeader: createAnimation(
{
transform: 'translateX(-40px)',
opacity: '0',
}, {
},
{
transition: 'opacity 150ms ease-out, transform 300ms ease-out',
transform: 'translateX(0px)',
opacity: '1',
},
300
300,
),
text: createAnimation({
text: createAnimation(
{
transform: 'translateY(-20px)',
opacity: '0',
}, {
},
{
transition: 'opacity 150ms ease-out, transform 300ms ease-out',
transform: 'translateY(0px)',
opacity: '1',
},
600
600,
),
items: itemsInRing.map((item, i) => (createAnimation({
items: itemsInRing.map((item, i) =>
createAnimation(
{
transform: 'translateX(-40px)',
opacity: '0',
},
{
transition: 'opacity 150ms ease-out, transform 300ms ease-out',
transform: 'translateX(0px)',
opacity: '1',
},
400 + 100 * i,
),
),
footer: createAnimation(
{
transition: 'opacity 150ms ease-out, transform 300ms ease-out',
transform: 'translateX(-40px)',
opacity: '0',
}, {
},
{
transition: 'opacity 150ms ease-out, transform 300ms ease-out',
transform: 'translateX(0px)',
opacity: '1',
},
400 + 100 * i
))),
footer: createAnimation({
transition: 'opacity 150ms ease-out, transform 300ms ease-out',
transform: 'translateX(-40px)',
opacity: '0',
}, {
transition: 'opacity 150ms ease-out, transform 300ms ease-out',
transform: 'translateX(0px)',
opacity: '1',
},
600 + itemsInRing.length * 100
600 + itemsInRing.length * 100,
),
};
@@ -77,7 +90,7 @@ class PageItem extends React.Component {
background: createAnimation(
this.animationsIn.background.stateB,
this.animationsIn.background.stateA,
300 + itemsInRing.length * 50
300 + itemsInRing.length * 50,
),
navHeader: createAnimation(
this.animationsIn.navHeader.stateB,
@@ -86,7 +99,7 @@ class PageItem extends React.Component {
transform: 'translateX(40px)',
opacity: '0',
},
0
0,
),
text: createAnimation(
this.animationsIn.text.stateB,
@@ -95,43 +108,58 @@ class PageItem extends React.Component {
transition: 'opacity 150ms ease-out, transform 300ms ease-out',
opacity: '0',
},
0
0,
),
items: itemsInRing.map((item, i) => (createAnimation(
this.animationsIn.items[i].stateB,
{
transition: 'opacity 150ms ease-out, transform 300ms ease-out',
transform: 'translateX(40px)',
opacity: '0',
},
100 + 50 * i
))),
footer: createAnimation(
this.animationsIn.text.stateB,
items: itemsInRing.map((item, i) =>
createAnimation(
this.animationsIn.items[i].stateB,
{
transition: 'opacity 150ms ease-out, transform 300ms ease-out',
transform: 'translateX(40px)',
opacity: '0',
},
200 + itemsInRing.length * 50
100 + 50 * i,
),
),
footer: createAnimation(
this.animationsIn.text.stateB,
{
transition: 'opacity 150ms ease-out, transform 300ms ease-out',
transform: 'translateX(40px)',
opacity: '0',
},
200 + itemsInRing.length * 50,
),
};
if (props.leaving) { // entering from an other page
this.state = setAnimations({}, createAnimationRunner(this.animationsIn).getState());
} else { // Hard refresh
if (props.leaving) {
// entering from an other page
this.state = setAnimations(
{},
createAnimationRunner(this.animationsIn).getState(),
);
} else {
// Hard refresh
this.state = {};
}
}
componentWillReceiveProps({ leaving }) {
if (!this.props.leaving && leaving) { // page will be left
this.animationRunner = createAnimationRunner(this.animationsOut, this.handleAnimationsUpdate);
if (!this.props.leaving && leaving) {
// page will be left
this.animationRunner = createAnimationRunner(
this.animationsOut,
this.handleAnimationsUpdate,
);
this.animationRunner.run();
this.animationRunner.awaitAnimationComplete(this.props.onLeave);
}
if (this.props.leaving && !leaving) { // page is entered
this.animationRunner = createAnimationRunner(this.animationsIn, this.handleAnimationsUpdate);
if (this.props.leaving && !leaving) {
// page is entered
this.animationRunner = createAnimationRunner(
this.animationsIn,
this.handleAnimationsUpdate,
);
this.animationRunner.run();
}
}
@@ -140,20 +168,22 @@ class PageItem extends React.Component {
this.setState(setAnimations(this.state, this.animationRunner.getState()));
};
getAnimationState = (name) => {
getAnimationState = name => {
if (!this.state.animations) {
return undefined;
}
return this.state.animations[name];
};
getItem = (props) => {
getItem = props => {
const [quadrantName, itemName] = props.pageName.split('/');
const item = props.items.filter(item => item.quadrant === quadrantName && item.name === itemName)[0];
const item = props.items.filter(
item => item.quadrant === quadrantName && item.name === itemName,
)[0];
return item;
}
};
getItemsInRing = (props) => {
getItemsInRing = props => {
const item = this.getItem(props);
const itemsInRing = groupByQuadrants(props.items)[item.quadrant][item.ring];
return itemsInRing;
@@ -168,7 +198,10 @@ class PageItem extends React.Component {
<div className="item-page">
<div className="item-page__nav">
<div className="item-page__nav__inner">
<div className="item-page__header" style={this.getAnimationState('navHeader')}>
<div
className="item-page__header"
style={this.getAnimationState('navHeader')}
>
<h3 className="headline">{translate(item.quadrant)}</h3>
</div>
@@ -180,33 +213,55 @@ class PageItem extends React.Component {
>
<div className="split">
<div className="split__left">
<Badge big type={item.ring}>{item.ring}</Badge>
<Badge big type={item.ring}>
{item.ring}
</Badge>
</div>
<div className="split__right">
<Link className="icon-link" pageName={item.quadrant}>
<span className="icon icon--pie icon-link__icon"></span>Quadrant Overview
<span className="icon icon--pie icon-link__icon" />Quadrant
Overview
</Link>
</div>
</div>
</ItemList>
<div className="item-page__footer" style={this.getAnimationState('footer')}>
<div
className="item-page__footer"
style={this.getAnimationState('footer')}
>
<FooterEnd modifier="in-sidebar" />
</div>
</div>
</div>
<div className="item-page__content" style={this.getAnimationState('background')}>
<div className="item-page__content__inner" style={this.getAnimationState('text')}>
<div
className="item-page__content"
style={this.getAnimationState('background')}
>
<div
className="item-page__content__inner"
style={this.getAnimationState('text')}
>
<div className="item-page__header">
<div className="split">
<div className="split__left">
<h1 className="hero-headline hero-headline--inverse">{item.title}</h1>
<h1 className="hero-headline hero-headline--inverse">
{item.title}
</h1>
</div>
<div className="split__right">
<Badge big type={item.ring}>{item.ring}</Badge>
<Badge big type={item.ring}>
{item.ring}
</Badge>
</div>
</div>
</div>
<div className="markdown" dangerouslySetInnerHTML={{__html: item.body}} />
<div
className="markdown"
dangerouslySetInnerHTML={{ __html: item.body }}
/>
{item.revisions.length > 1 && (
<ItemRevisions revisions={item.revisions.slice(1)} />
)}
</div>
</div>
</div>

View File

@@ -4,6 +4,7 @@ import ItemList from './ItemList';
import Link from './Link';
import Fadeable from './Fadeable';
import SetTitle from './SetTitle';
import ItemRevisions from './ItemRevisions';
import { translate } from '../../common/config';
import { groupByQuadrants } from '../../common/model';
@@ -43,6 +44,7 @@ class PageItem extends React.Component {
</div>
</div>
<div className="markdown" dangerouslySetInnerHTML={{__html: item.body}} />
{item.revisions.length > 1 && <ItemRevisions revisions={item.revisions.slice(1)} />}
</div>
</div>
</div>

View File

@@ -7,6 +7,7 @@ import Link from './Link';
import Search from './Search';
import Fadeable from './Fadeable';
import SetTitle from './SetTitle';
import Flag from './Flag';
import { groupByFirstLetter } from '../../common/model';
import { translate } from '../../common/config';
@@ -14,11 +15,15 @@ const rings = ['all', 'assess', 'trial', 'hold', 'adopt'];
const containsSearchTerm = (text = '', term = '') => {
// TODO search refinement
return text.trim().toLocaleLowerCase().indexOf(term.trim().toLocaleLowerCase()) !== -1;
}
return (
text
.trim()
.toLocaleLowerCase()
.indexOf(term.trim().toLocaleLowerCase()) !== -1
);
};
class PageOverview extends React.Component {
constructor(props, ...args) {
super(props, ...args);
this.state = {
@@ -36,30 +41,32 @@ class PageOverview extends React.Component {
}
}
handleRingClick = (ring) => (e) => {
handleRingClick = ring => e => {
e.preventDefault();
this.setState({
ring,
});
}
};
isRingActive(ringName) {
return this.state.ring === ringName;
}
itemMatchesRing = (item) => {
itemMatchesRing = item => {
return this.state.ring === 'all' || item.ring === this.state.ring;
};
itemMatchesSearch = (item) => {
return this.state.search.trim() === '' ||
itemMatchesSearch = item => {
return (
this.state.search.trim() === '' ||
containsSearchTerm(item.title, this.state.search) ||
containsSearchTerm(item.body, this.state.search) ||
containsSearchTerm(item.info, this.state.search);
containsSearchTerm(item.info, this.state.search)
);
};
isItemVisible = (item) => {
isItemVisible = item => {
return this.itemMatchesRing(item) && this.itemMatchesSearch(item);
};
@@ -69,11 +76,13 @@ class PageOverview extends React.Component {
...group,
items: group.items.filter(this.isItemVisible),
}));
const nonEmptyGroups = groupsFiltered.filter(group => group.items.length > 0);
const nonEmptyGroups = groupsFiltered.filter(
group => group.items.length > 0,
);
return nonEmptyGroups;
}
handleSearchTermChange = (value) => {
handleSearchTermChange = value => {
this.setState({
search: value,
});
@@ -91,66 +100,67 @@ class PageOverview extends React.Component {
<div className="filter">
<div className="split split--filter">
<div className="split__left">
<Search onChange={this.handleSearchTermChange} value={this.state.search} />
<Search
onChange={this.handleSearchTermChange}
value={this.state.search}
/>
</div>
<div className="split__right">
<div className="nav">
{
rings.map(ringName => (
<div className="nav__item" key={ringName}>
<Badge
big
onClick={this.handleRingClick(ringName)}
type={this.isRingActive(ringName) ? ringName : 'empty' }
>
{ringName}
</Badge>
</div>
))
}
{rings.map(ringName => (
<div className="nav__item" key={ringName}>
<Badge
big
onClick={this.handleRingClick(ringName)}
type={this.isRingActive(ringName) ? ringName : 'empty'}
>
{ringName}
</Badge>
</div>
))}
</div>
</div>
</div>
</div>
<div className="letter-index">
{
groups.map(({ letter, items }) => (
<div key={letter} className="letter-index__group">
<div className="letter-index__letter">{letter}</div>
<div className="letter-index__items">
<div className="item-list">
<div className="item-list__list">
{
items.map((item) => (
<Link
key={item.name}
className="item item--big item--no-leading-border item--no-trailing-border"
pageName={`${item.quadrant}/${item.name}`}
>
<div className="split split--overview">
<div className="split__left">
<div className="item__title">{item.title}</div>
{groups.map(({ letter, items }) => (
<div key={letter} className="letter-index__group">
<div className="letter-index__letter">{letter}</div>
<div className="letter-index__items">
<div className="item-list">
<div className="item-list__list">
{items.map(item => (
<Link
key={item.name}
className="item item--big item--no-leading-border item--no-trailing-border"
pageName={`${item.quadrant}/${item.name}`}
>
<div className="split split--overview">
<div className="split__left">
<div className="item__title">
{item.title}
<Flag item={item} />
</div>
</div>
<div className="split__right">
<div className="nav nav--relations">
<div className="nav__item">
{translate(item.quadrant)}
</div>
<div className="split__right">
<div className="nav nav--relations">
<div className="nav__item">{translate(item.quadrant)}</div>
<div className="nav__item">
<Badge type={item.ring}>{item.ring}</Badge>
</div>
</div>
<div className="nav__item">
<Badge type={item.ring}>{item.ring}</Badge>
</div>
</div>
</Link>
))
}
</div>
</div>
</div>
</Link>
))}
</div>
</div>
</div>
))
}
</div>
))}
</div>
</Fadeable>
);

View File

@@ -6,10 +6,10 @@ import Fadeable from './Fadeable';
import SetTitle from './SetTitle';
import { translate } from '../../common/config';
import { groupByQuadrants } from '../../common/model';
import {featuredOnly, groupByQuadrants} from '../../common/model';
export default function PageQuadrant({ leaving, onLeave, pageName, items, ...props }) {
const groups = groupByQuadrants(items);
const groups = groupByQuadrants(featuredOnly(items));
return (
<Fadeable leaving={leaving} onLeave={onLeave}>
<SetTitle {...props} title={translate(pageName)} />

View File

@@ -3,6 +3,7 @@ import { translate, rings } from '../../common/config';
import Badge from './Badge';
import Link from './Link';
import ItemList from './ItemList';
import Flag from './Flag';
const renderList = (ringName, quadrantName, groups, big) => {
const itemsInRing = groups[quadrantName][ringName];
@@ -10,7 +11,9 @@ const renderList = (ringName, quadrantName, groups, big) => {
if (big === true) {
return (
<ItemList items={itemsInRing} noLeadingBorder>
<Badge type={ringName} big={big}>{ringName}</Badge>
<Badge type={ringName} big={big}>
{ringName}
</Badge>
</ItemList>
);
}
@@ -20,23 +23,24 @@ const renderList = (ringName, quadrantName, groups, big) => {
<div className="ring-list__header">
<Badge type={ringName}>{ringName}</Badge>
</div>
{
itemsInRing.map(item => (
<span
key={item.name}
className="ring-list__item"
>
<Link className="link" pageName={`${item.quadrant}/${item.name}`}>{item.title}</Link>
</span>
))
}
{itemsInRing.map(item => (
<span key={item.name} className="ring-list__item">
<Link className="link" pageName={`${item.quadrant}/${item.name}`}>
{item.title}
<Flag item={item} short />
</Link>
</span>
))}
</div>
);
}
};
const renderRing = (ringName, quadrantName, groups, big) => {
if (!groups[quadrantName] || !groups[quadrantName][ringName] || groups[quadrantName][ringName].length === 0) {
if (
!groups[quadrantName] ||
!groups[quadrantName][ringName] ||
groups[quadrantName][ringName].length === 0
) {
return null;
}
return (
@@ -44,7 +48,7 @@ const renderRing = (ringName, quadrantName, groups, big) => {
{renderList(ringName, quadrantName, groups, big)}
</div>
);
}
};
export default function QuadrantSection({ quadrantName, groups, big = false }) {
return (
@@ -54,21 +58,18 @@ export default function QuadrantSection({ quadrantName, groups, big = false }) {
<div className="split__left">
<h4 className="headline">{translate(quadrantName)}</h4>
</div>
{
!big && (
<div className="split__right">
<Link className="icon-link" pageName={`${quadrantName}`}>
<span className="icon icon--pie icon-link__icon"></span>Quadrant Overview
</Link>
</div>
)
}
{!big && (
<div className="split__right">
<Link className="icon-link" pageName={`${quadrantName}`}>
<span className="icon icon--pie icon-link__icon" />Quadrant
Overview
</Link>
</div>
)}
</div>
</div>
<div className="quadrant-section__rings">
{
rings.map((ringName) => renderRing(ringName, quadrantName, groups, big))
}
{rings.map(ringName => renderRing(ringName, quadrantName, groups, big))}
</div>
</div>
);

6
js/date.js Normal file
View File

@@ -0,0 +1,6 @@
import moment from 'moment';
const isoDateToMoment = isoDate => moment(isoDate, 'YYYY-MM-DD');
export const formatRelease = isoDate =>
isoDateToMoment(isoDate).format('MMMM YYYY');

View File

@@ -4,49 +4,50 @@
"description": "",
"main": "index.js",
"scripts": {
"build": "npm run clean && npm run build:pages && npm run build:jsprod && npm run build:css && npm run build:assets",
"build:all": "npm run build",
"build": "yarn clean && yarn build:pages && yarn build:jsprod && yarn build:css && yarn build:assets",
"build:all": "yarn build",
"build:pages": "cross-env RENDER_MODE=server babel-node ./tasks/build.js",
"build:js": "cross-env RENDER_MODE=client webpack --config webpack.config.js",
"build:jsprod": "cross-env RENDER_MODE=client webpack -p --config webpack.config.js",
"build:css": "postcss -c postcss.config.json -o dist/techradar/assets/css/styles.css styles/main.css",
"build:css": "postcss -c postcss.config.js -o dist/techradar/assets/css/styles.css styles/main.css",
"build:assets": "babel-node ./tasks/assets.js",
"watch": "babel-node ./tasks/watch.js",
"clean": "babel-node ./tasks/clean.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "AOE GmbH <contact-de@aoe.com> (http://www.aoe.com)",
"license": "MIT",
"license": "Apache-2.0",
"dependencies": {
"async": "2.1.4",
"autoprefixer": "6.7.1",
"babel-cli": "6.22.2",
"babel-loader": "6.2.10",
"babel-polyfill": "6.23.0",
"babel-preset-latest": "6.22.0",
"babel-preset-react": "6.23.0",
"babel-preset-stage-0": "6.22.0",
"async": "2.6.0",
"autoprefixer": "7.1.6",
"babel-cli": "6.26.0",
"babel-core": "6.26.0",
"babel-loader": "7.1.2",
"babel-polyfill": "6.26.0",
"babel-preset-latest": "6.24.1",
"babel-preset-react": "6.24.1",
"babel-preset-stage-0": "6.24.1",
"classnames": "2.2.5",
"cross-env": "^4.0.0",
"css-mqpacker": "^5.0.1",
"front-matter": "2.1.2",
"fs-extra": "2.0.0",
"highlight.js": "9.10.0",
"history": "4.5.1",
"cross-env": "^5.1.1",
"css-mqpacker": "^6.0.1",
"front-matter": "2.3.0",
"fs-extra": "4.0.2",
"highlight.js": "9.12.0",
"history": "4.7.2",
"live-server": "1.2.0",
"marked": "0.3.9",
"moment": "2.17.1",
"postcss-cli": "2.6.0",
"postcss-css-variables": "0.6.0",
"postcss-custom-media": "^5.0.1",
"postcss-easy-import": "2.0.0",
"postcss-nested": "1.0.0",
"react": "15.4.2",
"react-dom": "15.4.2",
"react-redux": "5.0.2",
"redux": "3.6.0",
"moment": "2.22.1",
"postcss-cli": "4.1.1",
"postcss-css-variables": "0.8.0",
"postcss-custom-media": "^6.0.0",
"postcss-easy-import": "3.0.0",
"postcss-nested": "2.1.2",
"react": "16.1.1",
"react-dom": "16.1.1",
"react-redux": "5.0.6",
"redux": "3.7.2",
"walk": "2.3.9",
"webpack": "2.2.0"
"webpack": "3.8.1"
},
"repository": {
"type": "git",

19
postcss.config.js Normal file
View File

@@ -0,0 +1,19 @@
const postcssEasyImport = require('postcss-easy-import');
const postcssNested = require('postcss-nested');
const postcssCustomMedia = require('postcss-custom-media');
const postcssCssVariables = require('postcss-css-variables');
const postcssMqPacker = require('css-mqpacker');
const postcssAutoprefixer = require('autoprefixer');
module.exports = {
plugins: [
postcssEasyImport(),
postcssNested(),
postcssCustomMedia(),
postcssCssVariables(),
postcssMqPacker(),
postcssAutoprefixer({
browsers: '> 5%',
}),
],
};

View File

@@ -1,13 +0,0 @@
{
"use": [
"postcss-easy-import",
"postcss-nested",
"postcss-custom-media",
"postcss-css-variables",
"css-mqpacker",
"autoprefixer"
],
"autoprefixer": {
"browsers": "> 5%"
}
}

View File

@@ -1,16 +0,0 @@
---
title: "Angular 2"
ring: assess
quadrant: languages-and-frameworks
---
The latest version of the Angular Framework, which is used for large single-page applications.
[Angular 2](https://angular.io/) is a complete rewrite of Angular 1 — many things have changed compared to the first version. The latest best practices and toolings from the JavaScript community have found their way into Angular2.
It supports DI (dependency injection), it has a clean inheritance and a good separation of concerns. Angular2 follows the [web component standards](https://www.w3.org/standards/techs/components#w3c_all) to avoid negative side effects between components.
We think that Angular2+ is well-structured on both a development and an application level.
When talking about Angular2, we must consider the [angular.cli](https://cli.angular.io/) as well, which provides a huge level of intelligent automation along the development process and project setup.

View File

@@ -0,0 +1,16 @@
---
title: "Angular"
ring: assess
quadrant: languages-and-frameworks
---
The latest version of the Angular Framework, which is used for large single-page applications.
[Angular](https://angular.io/) is a complete rewrite of Angular 1 — many things have changed compared to the first version. The latest best practices and toolings from the JavaScript community have found their way into Angular.
It supports DI (dependency injection), it has a clean inheritance and a good separation of concerns. Angular follows the [web component standards](https://www.w3.org/standards/techs/components#w3c_all) to avoid negative side effects between components.
We think that Angular is well-structured on both a development and an application level.
When talking about Angular, we must consider the [angular.cli](https://cli.angular.io/) as well, which provides a huge level of intelligent automation along the development process and project setup.

View File

@@ -7,28 +7,29 @@ quadrant: languages-and-frameworks
[Babel](https://babeljs.io/) gives you the possibility to use the latest features from JavaScript ([ECMAScript](https://en.wikipedia.org/wiki/ECMAScript)) in the browser of your choice.
Without Babel in the backbone; you had to use the feature set of your oldest browser or use feature detections such as [modernizr](https://modernizr.com/) or write polyfills on your own.
Without Babel you had to use the feature set of your oldest browser or use feature detections such as [modernizr](https://modernizr.com/) or write polyfills on your own.
In general, Babel is split in 2 ways to bring you the new goodies you want.
1. New syntax will be compiled to old EcmaScript 5 code e.g.:
1. New syntax will be compiled to old EcmaScript 5 code e.g.:
* [arrow-functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)
* [generators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator)
* [destructing](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)
* [template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals)
* [...](https://babeljs.io/learn-es2015/)
2. New globals and functions are provided by [babel-polyfill](http://babeljs.io/docs/usage/polyfill/) e.g.:
* [arrow-functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)
* [generators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator)
* [destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)
* [template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals)
* [...](https://babeljs.io/learn-es2015/)
* [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
* [Array.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find)
* [Array.includes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes)
* [...](https://github.com/zloirock/core-js#index)
2. New globals and functions are provided by [babel-polyfill](http://babeljs.io/docs/usage/polyfill/) e.g.:
The configuration is really simple due to the [plugin system](http://babeljs.io/docs/plugins/). You can choose which ECMAScript version and [stage presets](http://babeljs.io/docs/plugins/) you want to use.
* [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
* [Array.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find)
* [Array.includes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes)
* [...](https://github.com/zloirock/core-js#index)
* for the latest ECMAScript version use [babel-preset-latest](https://babeljs.io/docs/plugins/preset-latest/)
* for version 2015 only use [babel-preset-2015](https://babeljs.io/docs/plugins/preset-es2015/)
The configuration is really simple due to the [plugin system](http://babeljs.io/docs/plugins/). You can choose which ECMAScript version and [stage presets](http://babeljs.io/docs/plugins/#presets) you want to use.
* for the latest ECMAScript version use [babel-preset-env](https://babeljs.io/docs/plugins/preset-env/)
* for version 2015 only use [babel-preset-2015](https://babeljs.io/docs/plugins/preset-es2015/)
To know what you need you can practice ECMAScript 6 by doing it with [es6katas](http://es6katas.org/) and ask [caniuse](http://caniuse.com/).

View File

@@ -6,4 +6,4 @@ quadrant: platforms-and-aoe-services
---
Elasticsearch is a REST-based search and analytics engine based on Lucene. Unlike its competitor Apache Solr, it was developed in the beginning with clustering and scaling in mind. It allows you to create complex queries while still delivering results very fast.
At AOE, we use Elasticsearch for logging as well as our own search solution [Searchperience](http://www.searchperience.com/). We recently moved the Searchperience stack from Solr to Elasticsearch and think this was the right decision. Especially in terms of scaling, ease of use and performance, Elasticsearch really shines. Also, the API design took some of the learnings from Apache SOLR into account - for example, the queryDSL is a powerful way of describing different search use cases with highly flexible support of aggregations, etc.
At AOE, we use Elasticsearch for logging as well as our own search solution [Searchperience®](http://www.searchperience.com/). We recently moved the Searchperience stack from Solr to Elasticsearch and think this was the right decision. Especially in terms of scaling, ease of use and performance, Elasticsearch really shines. Also, the API design took some of the learnings from Apache SOLR into account - for example, the queryDSL is a powerful way of describing different search use cases with highly flexible support of aggregations, etc.

View File

@@ -4,9 +4,9 @@ ring: assess
quadrant: tools
---
[Jest](https://facebook.github.io/jest/) is a javascript testing framework by facebook to test javascript code **and** react applications / components.
[Jest](https://facebook.github.io/jest/) is a javascript testing framework by facebook to test javascript code **and** react applications / components.
We started using Jest (and [watchmen](https://github.com/facebook/watchman)) instead of Karma because it:
We started using Jest (and [watchmen](https://github.com/facebook/watchman)) instead of Karma because it:
- gives us integrated mocking library
- gives us integrated support for testing "promises"
@@ -15,4 +15,4 @@ We started using Jest (and [watchmen](https://github.com/facebook/watchman)) in
- gives us parallel test execution
- gives us snapshot testing for react components
It is easy to set up. And even if you have a running setup with karma/chai you can easily replace karma with jest. With a small [workaround](https://medium.com/@RubenOostinga/combining-chai-and-jest-matchers-d12d1ffd0303#.3callo273), chai and jest test matchers work fine together.
It is easy to set up. And even if you have a running setup with karma/chai you can easily replace karma with jest. With a small [workaround](https://medium.com/@RubenOostinga/combining-chai-and-jest-matchers-d12d1ffd0303#.3callo273), chai and jest test matchers work fine together.

View File

@@ -2,21 +2,38 @@
title: "Pin external dependencies"
ring: adopt
quadrant: methods-and-patterns
---
A lot of applications have dependencies on other modules or components. We have used different approaches regarding how and when these dependencies are resolved and have agreed on using a method we call "Pin (External) dependencies".
This is especially relevant for script languages, where the dependency management references the code and not immutable prebuild binaries - and therefore resolves the complete transient dependencies on the fly.
A lot of applications have dependencies on other modules or components. We have
used different approaches regarding how and when these dependencies are resolved
and have agreed on using a method we call "Pin (External) dependencies".
This is especially relevant for script languages, where the dependency
management references the code and not immutable prebuild binaries - and
therefore resolves the complete transient dependencies on the fly.
Most of these package- or dependency management solutions support two artefacts:
* a semantic dependency definition. This defines the compatible versions of the required dependencies. (Composer: composer.json / NPM: package.json)
* a lock file defining the exact revisions of the dependencies and the transient dependencies (dependencies of dependencies). This is created after running the tool. (Composer: composer.lock / NPM: npm-shrinkwrap.json).
* a semantic dependency definition. This defines the compatible versions of the
required dependencies. (Composer: composer.json / NPM: package.json)
* a lock file defining the exact revisions of the dependencies and the transient
dependencies (dependencies of dependencies). This is created after running the
tool. (Composer: composer.lock / NPM: npm-shrinkwrap.json / yarn: yarn.lock).
We suggest the following:
* Keep the dependency definition AND the lock file in version control. This ensures that chained dependencies are also locked and you have changes of that file visible in your version control commit history. This helps finding issues or bugs that might relate to unintended updates in external modules or transient dependencies.
* Build Step: The application build step should use the the pinned versions (with the help of the lock file) to ensure that the same revisions of the dependent packages are used.
* It's also suggested to use local or central caches for the retrieval of packages. (E.g. [artifactory as composer and npm cache](/platforms-and-aoe-services/artifactory.html))
* Keep the dependency definition AND the lock file in version control. This
ensures that chained dependencies are also locked and you have changes of that
file visible in your version control commit history. This helps finding issues
or bugs that might relate to unintended updates in external modules or
transient dependencies.
* Build Step: The application build step should use the the pinned versions
(with the help of the lock file) to ensure that the same revisions of the
dependent packages are used.
* It's also suggested to use local or central caches for the retrieval of
packages. (E.g.
[artifactory as composer and npm cache](/platforms-and-aoe-services/artifactory.html))
For updating of dependencies define a process in the team. This can either be done on the dev-system or in a seperate automated CI job - both resulting in updated dependency definitions in the applications VCS.
For updating of dependencies define a process in the team. This can either be
done on the dev-system or in a seperate automated CI job - both resulting in
updated dependency definitions in the applications VCS.

View File

@@ -7,8 +7,8 @@ quadrant: languages-and-frameworks
In an increasingly microservice-oriented environment, it is crucial that all parties agree on a common language and wire format for data exchange.
JSON and XML are two very well-known formats for serialization of data, however they come with a few drawbacks. JSON is completely dynamic without any validation (though there is json-schema) and XML uses an extremely heavyweight syntax, which carries a huge overhead, so parsing and transport becomes quite slow.
JSON and XML are two well-known formats for serialization of data; however, they come with a few drawbacks. JSON is completely dynamic without any validation (though there is json-schema) and XML uses an extremely heavyweight syntax, which carries a huge overhead, so parsing and transport becomes quite slow.
Protobuf, amongst others, is an approach to solving this problem by using well-defined schemas to create language-specific code, which serializes/marshals and deserializes/unmarshals data. One of the key features is the built-in support for evolving schemas, it is easily possible to incrementally extend the definition while staying backwards-compatible and compose messages out of several sub-messages.
Protobuf, amongst others, is an approach to solving this problem by using well-defined schemas to create language-specific code, which serializes/marshals and deserializes/unmarshals data. One of the key features is the built-in support for evolving schemas; it is easily possible to incrementally extend the definition while staying backwards-compatible and compose messages consisting of several sub-messages.
If you are looking for a way to have different systems agree on a common protocol on top of a transport layer (such as AMQP or HTTP), Protobuf is definitely worth taking a look at and should be assessed.
If you are looking for a way to have different systems agree on a common protocol on top of a transport layer (such as AMQP or HTTP), Protobuf is definitely worth examining more closely and should be assessed.

View File

@@ -10,13 +10,12 @@ Puppet
Puppet is an Open Source configuration management tool. It is used by a wide range of different companies world-wide, e.g. the Wikimedia Foundation, Mozilla, Reddit, CERN, Dell, Rackspace, Twitter, the New York Stock Exchange, PayPal, Disney, Citrix Systems, Spotify, Oracle, the University of California Los Angeles, the University of North Texas, QVC, Intel, Google and others.
Puppet has been the basic tool to address Continuous Configuration Automation (CCA) in AOE's [Infrastructure as Code](/methods-and-patterns/infrastructure-as-code.html) strategy (IaC) for more than 4 years.
Puppet has been the basic tool to address Continuous Configuration Automation (CCA) in AOE's [Infrastructure as Code](/methods-and-patterns/infrastructure-as-code.html) strategy (IaC) for more than 4 years.
Puppet Environments
-------------------
Intended to give projects the means to develop and maintain their own infrastructure, separated and not influenced by other projects, Puppet environments, together with Puppet module versioning and ENC, have been introduced.\
Puppet Environments are rated "Trial". It supports our strategy of Infrastructure as Code (IaC) and links it to our DevOps approach, enabling project teams to set up and customize their own infrastructure. 
Puppet Environments are rated "Trial". It supports our strategy of Infrastructure as Code (IaC) and links it to our DevOps approach, enabling project teams to set up and customize their own infrastructure.
Teams that want to use the Puppet Environments service from the AOE IT Team will find detailed information about the implemented CI/CD process for this.

View File

@@ -2,23 +2,18 @@
title: "React.js"
ring: trial
quadrant: languages-and-frameworks
---
React claims to be "the V in MVC". But for us it is much more than that. React
improved the way we approach frontend applications as we build them. Its
functional way of writing components and its declarative JSX syntax help us to
build interactive UIs very efficiently. React's one-way data flow keeps
everything modular and fast and makes even large applications more readable.
React claims to be "the V in MVC". But for us it is much more than that. React improved the way we approach frontend applications as we build them. Its functional way of writing components
and its declarative JSX syntax help us to build interactive UIs very efficiently.
React's one-way data flow keeps everything modular and fast and makes even large applications more readable.
Components are the central point of React - once we fully started [thinking in react](https://facebook.github.io/react/docs/thinking-in-react.html), our components became smaller, more reusable and better testable.
After some 1.5 years of experience with React and the steady growth of the community and ecosystem around it,
we can confidently say that we still see great protential to build upcoming projects with React.
<!--except-->
Components are the central point of React - once we fully started
[thinking in react](https://facebook.github.io/react/docs/thinking-in-react.html),
our components became smaller, more reusable and better testable.
After some 1.5 years of experience with React and the steady growth of the
community and ecosystem around it, we can confidently say that we still see
great protential to build upcoming projects with React.

17
radar/2017-03-01/rxjs.md Normal file
View File

@@ -0,0 +1,17 @@
---
title: "RxJs"
ring: trial
quadrant: languages-and-frameworks
---
RX/JS aka reactive streams
RxJS is an implementation for the reactive programming paradigm which implements mostly the observer and iterator
pattern and follows the functional programming ideas. The pattern actually got a renaissance because it's not completely
new but has new implementations in many frameworks and languages like Angular, Akka, Spring and many more. Reason for
that attention actually is (in the javascript world), that observables can be cancelled (by rules too) and observables
can pass (stream) data on multiple events. Both aspects are not well realizable using promises e.g. and both were also
detected as a huge limitation in the JavaScript community — and so it's worth to get an understanding for reactive
programming in general.
We at AOE actually use RxJS in combination with Angular and think that it's worth to dive deeper into this paradigm.

View File

@@ -11,4 +11,4 @@ It is fully interoperable with Java but has a big ecosystem of tools and framewo
Scala provides one of the best high-level concurrency- and async features on the language level as well as on the framework level, making it the default choice of twitter and the like.
At AOE, we already use Scala in various projects to create scalable backend systems (Play, Akka) or for batch processing (Spark).
At AOE, we already use Scala in various projects to create scalable backend systems (Play, Akka) or for batch processing (Spark).

View File

@@ -13,10 +13,10 @@ JavaScript scoping, which led into recurring workarounds such as **var self = th
In TypeScript **this** stays **this**, which leads to more readable and understandable code from an OOP perspective.
TypeScript continues to be actively developed by Microsoft and is also well-Integrated in today's IDEs.
TypeScript continues to be actively developed by Microsoft and is well-Integrated in today's IDEs.
The excellent structure and the possibilities for extension make it a good choice to consider for larger JavaScript projects.
Typescript was the choice for [Angular 2+](/languages-and-frameworks/angular-2.html) and one can assume that it will get more traction with the success of Angular 2 in the future.
Typescript was the choice for [Angular](/languages-and-frameworks/angular.html) and one can assume that it will get more traction with the success of Angular in the future.
There are also projects that support Typescript „code execution“ on the server such as [ts-node](https://www.npmjs.com/package/ts-node).

View File

@@ -4,12 +4,12 @@ ring: trial
quadrant: tools
---
**[WireMock](http://wiremock.org/docs/)** is an HTTP mock server - it can be used to mock APIs for testing.
**[WireMock](http://wiremock.org/docs/)** is an HTTP mock server - it can be used to mock APIs for testing.
At its core, it is a web server that can be prepared to serve canned responses to particular requests (stubbing), and that captures incoming requests so that they can be checked later (verification). It also has an assortment of other useful features including record/playback of interactions with other APIs, injection of faults and delays, simulation of stateful behavior. 
At its core, it is a web server that can be prepared to serve canned responses to particular requests (stubbing), and that captures incoming requests so that they can be checked later (verification). It also has an assortment of other useful features including record/playback of interactions with other APIs, injection of faults and delays, simulation of stateful behavior.
It can be used as a library by any JVM application, or run as a standalone process either on the same host as the system under test or a remote server. All of WireMock's features are accessible via its REST (JSON) interface and its Java API. Additionally, the mock server can be configured via JSON files.
At AOE, we use WireMock as a standalone server to mock APIs that are outside our system context to get a stable environment for testing and rapid feedback. Besides the decoupled test and development advantages, the mocked APIs can also be used in contract-based tests. We also use embedded WireMock in functional tests to stub external services. The explicit test of faults are especially helpful in building and testing the [resilience of your application](/methods-and-patterns/resilience-thinking.html).
At AOE, we use WireMock as a standalone server to mock APIs that are outside our system context to get a stable environment for testing and rapid feedback. Besides the decoupled test and development advantages, the mocked APIs can also be used in contract-based tests. We also use embedded WireMock in functional tests to stub external services. The explicit test of faults are especially helpful in building and testing the [resilience of your application](/methods-and-patterns/resilience-thinking.html).
Because of the features such as flexible deployment, powerful request matching and record/payback interactions, as well as the fact that the server runs stable in our project environments, we classify WireMock as *trial*.
Because of the features such as flexible deployment, powerful request matching and record/payback interactions, as well as the fact that the server runs stable in our project environments, we classify WireMock as *trial*.

26
radar/2018-03-01/adr.md Normal file
View File

@@ -0,0 +1,26 @@
---
title: "ADR"
ring: assess
quadrant: methods-and-patterns
---
Architecture Decision Records
ADR is a lightweight documentation of important architecture decisions taken by the team.
Without documentation of the architecture and the architecture decisions, new team members can only do two things:
* either (blindy) accept what they find and see or
* (blindy) change things
It goes without saying that both options aren't right.
Therefore, we suggest documenting the important architecture decisions. We use a simple tool such as https://github.com/npryce/adr-tools and store them in version control.
In larger projects with many teams we also establish a regular "architecture board / COI" with regular meetings.
Often, the architecture decisions are taken in such meetings.
The main purpose of this documentation is to:
* inform new team members about the previous architecture decisions and their purpose and backgrounds
* inform the whole team (including all people who were absent)
* create documentation that can be used to remember things (e.g. conventions, patterns, etc.)

View File

@@ -0,0 +1,33 @@
---
title: "Akka Streams"
ring: assess
quadrant: languages-and-frameworks
---
In our backend services, we frequently encounter the task to transform data
coming from and uploading to external sources and services.
Building more complex data transformation processes with Akka Actors has proven
very difficult for us in the past.
Seeing this data as a stream of elements could allow handling them piece by
piece and only keeping as much of the data in-process as can currently be
handled.
[Akka Streams](http://doc.akka.io/docs/akka/current/scala/stream/index.html) is
a [Reactive Streams](http://www.reactive-streams.org/) implementation that
provides a very end-user friendly API for setting up streams for data
processing that are bounded in resource usage and efficient. It uses the Akka
Actor Framework to execute these streams in an asynchronous and parallel
fashion exploiting today's multi-core architectures without having the user to
interact with Actors directly. It handles things such as message resending in
failure cases and preventing message overflow. It is also interoperable with
other Reactive Streams implementations.
Our first trials with Akka Streams were promising but we haven't yet implemented
complex services with it.
We will continue looking into it together with the
[Alpakka](/languages-and-frameworks/alpakka.html) Connectors for integration
work.

View File

@@ -0,0 +1,19 @@
---
title: "Alpakka"
ring: assess
quadrant: languages-and-frameworks
---
When using [Akka Streams](/languages-and-frameworks/akka-streams.html) to build
reactive data transformation services you usually need to connect to several
different services such as FTP, S3 buckets, AMQP brokers or different databases.
[Alpakka](https://developer.lightbend.com/docs/alpakka/current/) provides
integration building blocks for Akka Streams to access these services in a
reactive fashion and contains transformations for working with XML, CSV or
JSON structured data.
Combined, Akka Streams and Alpakka enable us to build small reactive
integration services with minimal resource consumption and good performance, and
are a good alternative to larger ESB solutions or integration tools.

View File

@@ -0,0 +1,17 @@
---
title: "Angular"
ring: trial
quadrant: languages-and-frameworks
---
In addition to numerous major upgrades from version 2 to 5, which often needed a "hands-on" approach, a lot has happened in the Angular
ecosystem in 2017. Specifically, the improvements in the HTTP-Client, which now requires less coding effort. Or
the vast improvements on angular.cli such as aot (ahead of time compile) for faster rendering, fewer requests and
much smaller builds, to just name the most important ones.
We have achieved particularly good results using Angular in large and medium-size projects. Actually,
it's our framework-of-choice in our telecommunication sector teams as a single-page application framework (SPA) for microservice front
ends.
The convenient scaffolding of unit- and end-to-end-tests provides a quality-driven workflow.
Also, the module- and component architecture helps to keep the codebase understandable end maintainable.

View File

@@ -0,0 +1,11 @@
---
title: "Artifactory"
ring: adopt
quadrant: platforms-and-aoe-services
---
Artifactory is now used in every newly started project at AOE and plays a
central role as an artifact repository for libraries, applications and docker
images. While cleanup is still an issue, we recommend the adoption of an
artifact repository in all our projects.

View File

@@ -0,0 +1,12 @@
---
title: "AsciiDoc"
ring: assess
quadrant: tools
---
AsciiDoc is a [lightweight markup language](https://en.wikipedia.org/wiki/Lightweight_markup_language) such as Markdown.
With a concise Syntax, it supports more features than Markdown without extensions such as Tables and Table of Contents.
It's easy to write complex documentation with AsciiDoc. And with Asciidoctor you can export your text to Pdf, HTML, etc.
At AOE, we use AsciiDoc for Documentation in our Repositories.

16
radar/2018-03-01/axure.md Normal file
View File

@@ -0,0 +1,16 @@
---
title: "Axure"
ring: trial
quadrant: tools
---
[Axure](https://www.axure.com/) is a tool that enables the creation of flowcharts, wireframes, mockups, user journeys and more.
Through features such as conditional logic, dynamic content and animations it is possible to create highly functional and rich UI prototypes, which convey a realistic look and feel as to how the application to be developed should behave and look.
We at AOE have used Axure successfully in several projects and it helped us a lot, particularly:
- as a basis for discussing how features should look, feel and work with customers
- as a basis for customers' discussions with other stakeholders
- as a basis for discussion and specifications for developers as to how features should look and behave
In conclusion, Axure is a great tool that provides all stakeholders with a common understanding and helped us a lot to specify requirements and find their implications.

10
radar/2018-03-01/babel.md Normal file
View File

@@ -0,0 +1,10 @@
---
title: "Babel"
ring: adopt
quadrant: languages-and-frameworks
---
We have been using babel for some time now. Since we have started using it, we don't have to
struggle with unimplemented features of ECMAScript. In this regard, JavaScript is
JavaScript, no matter what browser you are using. We we strongly recommend
using Babel or similar solutions (e.g. TypeScript).

View File

@@ -0,0 +1,17 @@
---
title: "Blameless Post Mortems"
ring: trial
quadrant: methods-and-patterns
---
> Failure and invention are inseparable twins.
>
> — <cite>Jeff Bezos</cite>
Blameless Post Mortems provide a concept of dealing with failures that inevitably occur when developing and operating complex software solutions. After any major incident or outage, the team gets together to perform an in-depth analysis of what happened and what can be done to mitigate the risk of similar issues happening in the future.
Based on trust, and under the assumption that every person involved had good intentions to do the best-possible job given the information at hand, Blameless Post Mortems provide an opportunity to continuously improve the quality of software and infrastructure and the processes to deal with critical situations.
The post mortem documentation usually consists of both a timeline of the events leading to an incident and the steps taken to its remediation, as well as future actions and learnings for increasing reslience and stability of our services.
At AOE, we strive to conduct a Blameless Post Mortem meeting after every user-visible incident.

View File

@@ -0,0 +1,3 @@
---
featured: false
---

38
radar/2018-03-01/crc.md Normal file
View File

@@ -0,0 +1,38 @@
---
title: "CRC Games"
ring: assess
quadrant: methods-and-patterns
---
Class Responsibility Collaboration Card Games are a method to discuss and align the software design - especially useful for object-oriented software.
A proper software design is one of the most important things to ensure the sucess and the maintainability of your software.
Especially for iterative development methods, where you work on a software task by task, it is important to have designs sessions that also look forward to the next iterations and the conceptional whole.
And for software design to be sucessfull, it is very important that everybody (in the team) has the same understanding of the design and stands behind it.
CRC sessions help to design and align the high-level object design and collaboration of your system with the whole team. During such sessions new team members can learn from the experience and explanations of tropers.
This is how we often conduct a CRC Session:
* Preparation:
* Make sure everybody has a high-level overview of the software (bounded context / use case overview). Because design should also focus on the long term.
* Choose some scenarios (such as "customer adds a promoted product to cart and sees the discounts...") that you want to discuss in this session
* Collect candidates for classes:
* For the first session, it makes sense to search for potential candidates for classes.
* Just put them on a whiteboard. Often nouns in scenarios are good candidates.
* Put the most promising ones on Post-its. (You can add more at any time)
* CRC session:
* 1 or 2 people stand up and try to explain the scenario with the help of the classes.
* This is similar to explaining a sequence diagramm and the cards are put on the table from left to right. During or after this, you can discuss design decisions and alternatives with the team.
* After this, someone else can stand up and present a potential alternative sequence.
* Once the team is aligned on a version they want to implement, it makes sense that it is repeated by different persons. Being exact is very important and avoids the situation where people can have an individual understanding of the model. The model and the collaboration lives in the heads of the people in the team therefore it is important that everyone understands it the same way.
* Closing:
* We are often not too enthusiatic about adding a list of "collaborators" to the cards, since the sequence explains this very well.
* So just take a picture and document the result somewhere, so that you can review the status for the next CRC session.
* Maybe some decisions are worth being documented in your [Architecture decision records](/methods-and-patterns/adr.html)

View File

@@ -0,0 +1,8 @@
---
title: "Docker"
ring: adopt
quadrant: platforms-and-aoe-services
---
Docker has pulled off very quickly and we updated it to "adopt".

View File

@@ -0,0 +1,10 @@
---
title: "Elasticsearch"
ring: adopt
quadrant: platforms-and-aoe-services
---
We are continuing to use Elasticsearch successfully in [Searchperience®] and have benefited from the aggregation features for related use cases such as rendering category trees.
We are also using Elasticsearch for some microservices as our persistence solution.
This is why we have updated its status to **adopt**.

3
radar/2018-03-01/flow.md Normal file
View File

@@ -0,0 +1,3 @@
---
featured: false
---

View File

@@ -0,0 +1,10 @@
---
title: "Gatling"
ring: adopt
quadrant: tools
---
Gatling is now the de-facto tool of choice for load testing in all of our
projects, having superseded JMeter completely. We therefore moved it to the
**Adopt** level.

View File

@@ -0,0 +1,16 @@
---
title: "Gitlab CI"
ring: assess
quadrant: tools
---
Until now, we have been using [Jenkins](https://jenkins.io/) for almost every single task that we have automated. With [Gitlab CI](https://about.gitlab.com/features/gitlab-ci-cd/) on the market, we have a number of new possibilities.
Some of the highlights are:
* Multi-platform you can execute builds on Unix, Windows, OSX, and any other platform that supports Go.
* Multi-language build scripts are command-line driven and work with Java, PHP, Ruby, C and any other language.
* Pipeline you can define multiple jobs per stage and you can trigger other builds.
* Autoscaling you can automatically spin up and down VM's to make sure your builds get processed immediately and minimize costs.
* Build artifacts you can upload binaries and other build artifacts to GitLab and browse and download them.
* Docker support you can use custom Docker images, spin up services as part of testing, build new Docker images, even run on Kubernetes.

View File

@@ -0,0 +1,13 @@
---
title: "Gitlab"
ring: trial
quadrant: tools
---
[Gitlab](https://about.gitlab.com/) provides nearly the same feature set as [Github](https://github.com/), but at a lower price. It also provides the possibility of hosting iternally, which is essential for us.
We are migrating more and more repositories from [gitolite](http://gitolite.com/gitolite/index.html), even from SVN to gitlab, as it provides a more stable and user friendly interface.
Gitlab also makes user/permission handling easier than our old gitolite. We don't need the IT team every time a new repository needs to be set up.

View File

@@ -0,0 +1,9 @@
---
title: "Go / Golang"
ring: trial
quadrant: languages-and-frameworks
---
We have moved Go to Trial because multiple teams have used Go with success for different services and tools.
The learning curve and productivity have proven to be immense and we are convinced that this language will find more adoption in other teams.

View File

@@ -0,0 +1,9 @@
---
title: "Grafana"
ring: assess
quadrant: platforms-and-aoe-services
---
[Grafana](https://grafana.com//) is an Open Source data visualization platform written in Go and NodeJS. It provides a vast choice of different graph types that can be easily combined into dashboards for displaying any kind of numerical or time-based data.
At AOE, we usually use Grafana in conjunction with [Prometheus](https://prometheus.io/) or [AWS CloudWatch](https://prometheus.io/) for visualizing both application and infrastructure metrics.

17
radar/2018-03-01/grpc.md Normal file
View File

@@ -0,0 +1,17 @@
---
title: "GRPC"
ring: assess
quadrant: languages-and-frameworks
---
gRPC, "A high-performance, Open Source, universal RPC framework," is a framework to easily connect clients and servers in an RPC setup.
gRPC was initially built at Google, and uses protobuf service definitions for method and payload specification.
Essentially, this makes it possible to define methods that a server exposes, with either a single payload or an incoming stream - either as a single response or a stream of responses.
The definition itself is carried out with the help of protobuf to define message types and method signatures, and then client and server interfaces are compiled for the language(s) you want. Currently there is support for languages such as C++, Java, Python, Go and many more.
The shared language-neutral protobuf definition allows you to create all code for all languages automatically and helps with the interoperability of different systems.
From a technical point of view, gRPC uses HTTP/2 as a transport, directly benefitting from the default TLS encryption.
Besides gRPC, other frameworks also use protobuf RPC definitions. These frameworks include twirp from twitch, which makes it easy to change the transport/control layer with only very small changes to the application code.
We at AOE plan to assess gRPC for microservice architectures which are more RPC style and less REST style.

3
radar/2018-03-01/gulp.md Normal file
View File

@@ -0,0 +1,3 @@
---
featured: false
---

View File

@@ -0,0 +1,12 @@
---
title: "HAL / HATEOAS"
ring: trial
quadrant: methods-and-patterns
---
We still recommend the usage of HAL and HATEOAS.
But, depending on the resource structure, there are some pitfalls to be aware of:
- Increased amount of HTTP calls
- Parallelization of client-side calls is more difficult or even impossible when following links on heavily nested resource structures
- Consumer side code might get larger

View File

@@ -0,0 +1,14 @@
---
title: "Helm/Terraform"
ring: trial
quadrant: tools
---
For the infrastructure of our OM3 projects we run multiple Kubernetes clusters, and to orchestrate the infrastructure provisioning we quickly decided to go with Terraform.
Terraform allows us to easily manage our infrastructure, from AWS EC2 instances to RabbitMQ message queues.
Also, the Kops installer for Kubernetes on AWS uses Terraform as its main building brick, and we can trigger Kops via Terraform.
For managing deployments within Kubernetes we use Helm, which makes templating Kubernetes configuration files super easy (also known as Helm charts).
We bring both tools together to manage similar parts of the infrastructure, for example a shared file with domainname to application mappings allows us to provision Route 53 DNS entries via Terraform and then roll out Kubernetes Ingress definitions with the appropriate hostname to service mapping via Helm.

View File

@@ -0,0 +1,3 @@
---
featured: false
---

View File

@@ -0,0 +1,13 @@
---
title: "Invision"
ring: trial
quadrant: tools
---
Invision is an online tool used to work and collaborate on design and prototypes and to share them between clients and the team.
We use it in many projects now to present prototypes and designs and it helps in understanding the planned user experience.
Also, we use this directly as a reference from the user stories to help the development teams in understanding and implementing the right frontend and backend functionalities.

8
radar/2018-03-01/jest.md Normal file
View File

@@ -0,0 +1,8 @@
---
title: "Jest"
ring: adopt
quadrant: tools
---
Updated to "adopt".

View File

@@ -0,0 +1,10 @@
---
title: "Keycloak"
ring: adopt
quadrant: tools
---
Most distributed systems still face a growing demand for user management, authentication, authorization and Single sign-on. In light of a growing security demand and specialization, the Open Source project JBoss Keycloak is a perfect match.
Keyloak has been a growing project from the outset and has a strong community. Keyloak is based on standards such as OAuth2, OIDC and SAML2. Securing a distributed system is supported by adapters, which are provided by Keycloak developers for different technology stacks. If there is no adapter for your technology stack, an integration on the protocol level with a library is simple. Many configurable features require no coding in the integrated projects. The required configuration is managed via code and promoted as usual.
We use Keycloak in our OM3 suite for several authentication-related use cases such as user management for system users and Single sign-on for customers. The OAuth access tokens can be used to secure APIs that access sensitive information. In addition, Keycloak is part of the AOE infrastructure and helps in securing the various services to support employees and customers.

View File

@@ -0,0 +1,13 @@
---
title: "Kubernetes"
ring: adopt
quadrant: platforms-and-aoe-services
---
Kubernetes has developed into the quasi-standard for container orchestration: Nearly every cloud provider provides managed Kubernetes, and even Docker Enterprise uses Kubernetes.
We are running several production systems with Kubernetes and we are using it in concepts such as:
* "secrets" and "configmaps" to manage configurations for the applications. By updating these resources with an automated configuration pipeline you have a great method for configuration management.
* Autoscaling of Kubernetes nodes and the usage of "horizontal pod scaling" inside Kubernetes allows elastic scaling
* The support of managing permissions with OAuth allows you to secure Kubernetes with [Keycloak](/tools/keycloak.html) (SSO)
* Kubernetes extensibility and API can be used for automation and customization. There is a growing ecosystem around extensions, which adds additional features.

View File

@@ -0,0 +1,3 @@
---
featured: false
---

View File

@@ -0,0 +1,17 @@
---
title: "Micro Frontends"
ring: assess
quadrant: methods-and-patterns
---
We see many benefits in Microservices especially in large teams but often this architecture
does not involve the user interface. Instead, you might end up maintaining a frontend monolith. With Micro Frontends
you enable your frontend developers to gain the same benefits that we have grown accustomed to in a Microservice architecture:
Decoupled components, which are developed and deployed by independent teams. But what sounds reasonable comes with
challenges. Integrating different Frontends on the client- or server-side can be tricky, as well as keeping the overall
User Experience consistent.
Despite the challenges, Micro Frontends help us to develop large applications across multiple teams. Developers can
work more independently without having too much trouble maintaining a large codebase. Being able to update oder
replace Frontend libraries in some parts of the application is yet another benefit in the fast-moving world of
frontend development.

View File

@@ -0,0 +1,11 @@
---
title: "Microservices"
ring: adopt
quadrant: methods-and-patterns
---
We continue to stand by are belief in the microservices concept. However, it's worth mentioning that we we had to learn some lessons when it came to resilient thinking and deployment-related dependencies between microservices.
We feel that our microservice-based applications are more robust than monolithic ones have been. Thanks to the
split of the overall complexity into multiple services, new employees or team members are becoming productive within days or a few weeks.

View File

@@ -0,0 +1,3 @@
---
featured: false
---

View File

@@ -0,0 +1,3 @@
---
featured: false
---

17
radar/2018-03-01/pact.md Normal file
View File

@@ -0,0 +1,17 @@
---
title: "PACT"
ring: trial
quadrant: tools
---
PACT (http://pact.io/) is a family of frameworks that provides support for *Consumer Driven Contract testing* accross different langauages and frameworks.
Consumer Driven Contract testing is a pattern for testing interfaces/boundaries between services.
It allows "consumers" to run tests against a defined Mock and record the defined interactions (=PACT).
It puts "providers" in the position to run the PACT tests inside theire Continuous Integration Pipelines, so that the provider knows if he might break any consumers.
This approach makes sense in organisations where teams collaborate more closely (See [Strategic Domain Driven Design](/methods-and-patterns/strategic-domain-driven-design.html) ), e.g. to build [Microservice oriented architectures](/methods-and-patterns/microservices.html)
Consumer Driven Contract Testing and how it can be conducted with PACT is documented very nicely on the official PACT website: https://docs.pact.io/.

View File

@@ -0,0 +1,3 @@
---
featured: false
---

3
radar/2018-03-01/phan.md Normal file
View File

@@ -0,0 +1,3 @@
---
featured: false
---

View File

@@ -0,0 +1,3 @@
---
featured: false
---

View File

@@ -0,0 +1,10 @@
---
title: "Pipeline as Code"
ring: adopt
quadrant: methods-and-patterns
---
We moved this pattern to **adopt**, because it is used by nearly every team and project now and is an important part of our automation.
For Jenkins, we often use a mix of Job DSL and Jenkins Pipelines and recently also used Gitlab Pipelines.

View File

@@ -0,0 +1,33 @@
---
title: "Ports and Adapters"
ring: trial
quadrant: methods-and-patterns
---
Ports and Adapters is an architecture or layering approach for software design. As with other layering approaches, it seperates different concerns in different layers, where dependencies are only allowed from the outside to the inside.
We use "ports and adapters" with success for (larger) applications, which contain certain business logic and/or provide several ways to access the services.
We often use the approach hand-in-hand with Domain Driven Design. In comparison with other layering patterns (e.g. layered architecture) it allows you to have a true technology-free core (domain) model. Why? Because, with the concept of "secondary ports" (=interfaces), it inverts the control and allows outer layers to provide adapters (=implementations of the defined interface).
It also defines clear boundaries regarding where to put what logic of your application.
You can find out more about the details and its origins in well-known blog posts such as [The Clean Architecture](https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html) or [Hexagonal architecture](http://alistair.cockburn.us/Hexagonal+architecture)
In short, here is how we often layer such applications:
* Domain:
* Is the inner layer and contains the technology-free domain model
* Often uses building blocks from Domain Driven Design
* It defines primary and secondary ports. (E.g. a secondary port is in "interface" that needs to be implemented in the infrastructure layer.)
* Application:
* Contains the Application's API and Services, that are developed around the use cases in the application requirements.
* These use cases orchestrate the flow of data to and from the domain
* Interfaces:
* Contain everything required to convert data from the format most-convenient for the use cases (e.g. in the application layer) to the format required for external agency/access
* e.g. (Web) Controllers / DTOs for handling forms, etc.
* Infrastructure:
* This layer is where all the (technical) details live. (The database and persistence is a detail, the communication format and mappings with external services is a detail, etc.)
* In this layer you typically have adapters for the secondary ports that have been defined in the layers below. (e.g. an Implementation of a Repository interface from the Domain layer)
These layers belong to every bounded context (modules) inside the application.
Are you searching for a potential timeless architecture for your critical application? Try implementing a potent technology-free domain model in the core layer and use ports and adapters to layer your application.

View File

@@ -0,0 +1,8 @@
---
title: "Puppet Environments"
ring: trial
quadrant: platforms-and-aoe-services
---
Puppet Environments has proven to work well for our projects using Puppet.

11
radar/2018-03-01/react.md Normal file
View File

@@ -0,0 +1,11 @@
---
title: "React.js"
ring: adopt
quadrant: languages-and-frameworks
---
The past months have shown that React is still a great fit for us for frontend-heavy
applications. With its rewritten core in version 16, Facebook shows how
important this framework is for them. Therefore, Facebook is investing a lot of effort into React and a
healthy community. In addition, we **REALLY** enjoy writing React
components so much so, that we have to move this library into **adopt**!

View File

@@ -0,0 +1,34 @@
---
title: "Reactive Programming"
ring: trial
quadrant: methods-and-patterns
---
Classic (web-) applications typically consist of transactions that submit
large forms to the server side, which then processes these and, in response, returns HTML
for the browser to render. Today's applications have more and more
fine-grained 'real-time'-like aspects: A simple modification of a form field
could trigger a complete roundtrip to the server including other services and
persistence. Naturally, all of these transactions should respect the
expectations of a user who wants a highly interactive application.
"Reactive Programming" tries to provide an answer to the challanges mentioned above
by raising the level of abstraction. This allows you to focus on the stream of
events that make up your business logic in a responsive, asynchronous fashion.
There are various descriptions of what Reactive Programming actually is - at
the most general level it is programming with asynchronous data streams and
contains tools to create, manipulate, combine and filter these streams. Under the term
"Reactive Programming", we summarize the principles and implementations that
underlie [ReactiveX](http://reactivex.io/) and the [Reactive
Manifesto](https://www.reactivemanifesto.org/).
"Reactive Programming" is employed in many of our services frontend and
backend but not always as an explicitly choosen pattern. As different
plattforms have different means to tackle this style of programming, we choose
to include "Reactive Programming" as a general Method and Patterns Item in
addition to concrete libraries and APIs such as
[Rx.JS](languages-and-frameworks/rxjs.html) or [Akka
Streams](/languages-and-frameworks/akka-streams.html) to highlight the
importance of the approach in general.

View File

@@ -0,0 +1,8 @@
---
title: "Scala Lang"
ring: adopt
quadrant: languages-and-frameworks
---
Scala is used in many projects at AOE. We have therefore moved it to the **adopt** level.

View File

@@ -0,0 +1,12 @@
---
title: "Self-service infrastructure"
ring: assess
quadrant: methods-and-patterns
---
With growing teams, growing projects and growing infrastructures, we decided to follow the "You build it, you run it" approach, and when we started to run Kubernetes, where we have a great abstraction layer between infrastructure and applications, we decided to make the developer teams write their own Helm charts.
By agreeing on just a couple of patters, this allows us to easily manage a microservice architecture with more than 60 Applications, without too much hassle managing infrastructure/runtimes for (among others) JVM, Go and PHP applications.
Most of the hosting/provisioning decisions are better kept within the team, as the teams know how their applications work. By providing a clear interface, this became the cornerstone for running our microservice architecture, and keeping the amount of actual servers much lower than in projects with a centralized operations/IT team.
Eventually, self-service infrastructure, and "You build it, you run it", allowed us to give both our application developers as well as our infrastructure engineers more flexibility than one team explaining to another team what to do, resulting in a better collaboration than before.

View File

@@ -0,0 +1,8 @@
---
title: "SonarQube"
ring: assess
quadrant: tools
---
At AOE, we're evaluating SonarQube to get an historical overview of the code quality of our Projects. With SonarQube, you can get a quick hint about the condition of your code. It analyzes many languages and provides numerous static analysis rules.

View File

@@ -0,0 +1,3 @@
---
featured: false
---

View File

@@ -0,0 +1,10 @@
---
title: "Spring Boot"
ring: trial
quadrant: languages-and-frameworks
---
We have had good experiences with Spring Boot, and already have several Spring Boot-based services running in
production. We like the ease of kickstarting new services and the variety of tools in the Spring ecosystem.

View File

@@ -0,0 +1,8 @@
---
title: "Styleguide Driven Development"
ring: adopt
quadrant: methods-and-patterns
---
Updated to "adopt".

View File

@@ -0,0 +1,3 @@
---
featured: false
---

8
radar/2018-03-01/vue.md Normal file
View File

@@ -0,0 +1,8 @@
---
title: "Vue.js"
ring: trial
quadrant: languages-and-frameworks
---
Updated to "trial".

View File

@@ -0,0 +1,12 @@
---
title: "Webpack"
ring: adopt
quadrant: tools
---
In the last few years, Webpack has grown to become the de-facto standard for Web
bundling in the JavaScript-Ecosystem. With Version 3, Webpack is a more robust
and better documented bundler with nice new features such as
[scope hoisting](https://webpack.js.org/plugins/module-concatenation-plugin/).
Because of this, and because of the continuously growing community, we have adopted Webpack for nearly
every single-page application we have.

View File

@@ -0,0 +1,3 @@
---
featured: false
---

11
radar/2018-03-01/yarn.md Normal file
View File

@@ -0,0 +1,11 @@
---
title: "Yarn"
ring: trial
quadrant: tools
---
Yarn is a dependency management tool for frontend (node) projects similar to npm. It also uses the npm registry and
infrastructure. According to Yarn, the benefits are that Yarn is much faster, automatically writes a .lock file and
builds up a local cache to be even faster when installing packages again.
At AOE, we started using Yarn in different projects to evaluate if we can switch to Yarn for all projects.

View File

@@ -1,10 +1,18 @@
.is-new {
.flag {
font-size: 9px;
background: var(--color-red);
display: inline-block;
padding: 3px 8px;
border-radius: 10px;
position: relative;
vertical-align: top;
margin-top: -2px;
left: 5px;
&--new {
background: var(--color-red);
}
&--changed {
background: var(--color-blue);
}
}

View File

@@ -3,4 +3,11 @@
@media (--until-sm) {
margin: 30px 0;
}
&--secondary {
margin: 10px 0;
@media (--until-sm) {
margin: 5px 0;
}
}
}

View File

@@ -4,4 +4,8 @@
color: var(--color-white);
font-size: 20px;
font-weight: normal;
&--dark {
color: var(--color-gray-light);
}
}

View File

@@ -0,0 +1,5 @@
.item-revision {
&+.item-revision {
margin-top: 40px;
}
}

View File

@@ -0,0 +1,3 @@
.item-revisions {
margin-top: 60px;
}

View File

@@ -29,6 +29,7 @@
}
&__footer {
margin-top: 5px;
flex: 0 0 auto;
}
}

View File

@@ -0,0 +1,8 @@
.publish-date {
color: var(--color-gray-normal);
text-align: right;
@media (--until-sm) {
text-align: center;
}
}

View File

@@ -1,24 +1,19 @@
import {
createRadar,
groupByQuadrants,
outputRadar,
} from '../common/radar';
import { createRadar, groupByQuadrants, outputRadar } from '../common/radar';
import { save } from '../common/file';
import { getPageNames } from '../common/config';
import { renderPage } from '../js/server';
(async () => {
try {
const radar = await createRadar();
getPageNames(radar).map((pageName) => {
getPageNames(radar).map(pageName => {
const pageHtml = renderPage(radar, pageName);
save(pageHtml, `${pageName}.html`);
});
console.log('Built radar');
} catch(e) {
} catch (e) {
console.error('error:', e);
}
})();

View File

@@ -11,23 +11,21 @@ import {
relativePath,
} from '../common/file';
const runBuild = (name) => (
exec(`npm run build:${name}`, (error, stdout, stderr) => {
const runBuild = name =>
exec(`yarn run build:${name}`, (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
console.log(stdout);
console.error(stderr);
})
);
});
const watchBuild = (name) => (eventType, fileName) => runBuild(name);
const watchBuild = name => (eventType, fileName) => runBuild(name);
const options = {
recursive: true,
}
};
runBuild('all');
@@ -37,8 +35,8 @@ watch(jsPath(), options, watchBuild('pages'));
watch(assetsPath(), options, watchBuild('assets'));
watch(radarPath(), options, watchBuild('pages'));
var params = {
root: relativePath('dist'),
logLevel: 2, // 0 = errors only, 1 = some, 2 = lots
const params = {
root: relativePath('dist'),
logLevel: 0, // 0 = errors only, 1 = some, 2 = lots
};
liveServer.start(params);

Some files were not shown because too many files have changed in this diff Show More