Build queries from Jekyll csv data file - csv

So I have a csv file with rows of data called "opportunities.csv"
This file is in the _data folder of my Jekyll site
client,opp,person,status
Oracle,"New thing five","Mary Smith",lead
Oracle,"Data plan","Sue Curry",lead
Oracle,"Migration 2019","Sue Curry",lead
IBM, "Platform assessment","Jane Campton",lost
I'd like to display the number of leads per person:
Mary Smith 1 lead
Sue Curry 2 lead
Jane Campion 0 lead
So, for each "person" in "site.opportunities" count the number of rows for each instance of "person" where "status" = "lead"
I now this involves incrementing counters inside nested for loops but I just can't seem to get it working.
I could change the data file format to json or yml but in my current use case I have other non-technical team members editing a csv file in excel so that's a given here.

You can do this with group_by and where filters :
{% assign groups = site.data.opportunities | group_by: "person" %}
<ul>
{% for g in groups %}
{% assign leads = g.items | where: "status", "lead" %}
<li>name : {{ g.name }} {{ leads.size }} leads</li>
{% endfor %}
</ul>

Related

Why converting to JSON a hash I initialized yield a null?

I am trying to build a hash in order to later output is as JSON (and ultimately import it to be reused by a a script). This is part of my static site built with jekyll.
Following the documentation on Expressions and Variables, I created a file with
---
---
{% assign aaa['bbb'] = 'xxx' %}
{{ aaa | jsonify }}
This was compiled by jekyll to a null (as if the hash was not initialized). Why is it so?
Sadly the documentation is talking about reading hash or arrays, not writing.
The only thing you can write from liquid is arrays.
create an empty array : {% assign my-array = "" | split: "/" %}{{
y-array | inspect }}
store with push or shift {% assign my-array = my-array | push: anything %}
= empty-array }}, where anything can be a string, an integer, a hash or an array.

Creating list of ip's in ansible using given range within jinja template

i want to generate a list of ip addresses (using range of last 8bits so 120-190 translates to x.x.x.120 - x.x.x.190) in defaults/main.yml in my role, and later use it to create new network interfaces and generate a new config file. I tried this approach:
defaults/main.yml:
ip_list: "{%for address_end in range(50,99)%} 192.168.0.{{address_end}}{%endfor%}"
conf_list: "{%for ip in ip_list%}server {{ip}}:6666 {%endfor%}"
and then to use it in template
template.conf.j2:
{% for conf_line in conf_list %}
{{conf_line}}
{% endfor %}
and all i got in generated config file was:
s
e
r
v
e
r
:
6
6
6
6
s
e
r
v
e
r
1
:
6
so my guess is that i'm not generating a list but just a long string and when I use for loop in template.conf.j2 I iterate over single chars. I tried using answer to this problem but all i got was syntax error. Any ideas for what might help me ?
You should format your vars as JSON lists if you want them to be lists.
1.1.1.1 2.2.2.2 3.3.3.3 is a string.
['1.1.1.1', '2.2.2.2', '3.3.3.3'] will be converted to list.
But there is alternative approach for you:
ip_list: "{{ lookup('sequence', 'start=50 count=12 format=192.168.0.%d', wantlist=True) }}"
conf_list: "{{ ip_list | map('regex_replace','(.*)','server \\1:6666') | list }}"
Kostantin answer was of much help, but i found just realized that generating config entries in my case could be solved in an less complex way. Instead of trying to iterate over list or a string variable variable in jinja template file template.conf.j2 like did with :
{% for conf_line in conf_list %}
{{conf_line}}
{% endfor %}
you could just enter insert new line signs while generating string in defaults/main.yml:
conf_list: "{%for ip in ip_list%}server {{ip}}:6666\n{%endfor%}"
and then just insert that whole string into a template.conf.j2 like this:
{{conf_line}}
nevertheless i have no other idea how to generate list of ip addresses other than the one Konstantin proposed.

Liquid where with and or or

I use liquid syntax with Jekyll. I use
{% assign projects = (site.pages | where: "categories" , "project") %}
For filter the pages with that categories. How can I add another criteria as "and" or "or"???
Ok,
I find the solution...very easy. Simple I put
site.categories['project'] | where: 'featured',true
and it filter with and and type

Creating data-structures for ansible templates

I'm trying to create a simple config file that enumerates all the (hostname, ip_address) pairs as part of an ansible task. What I'd really like to do is something like this (using ansible's global datastructures groups and hostvars):
def grouped_hosts():
ret = {}
for group in groups:
ret[group] = {}
for host in groups[group]:
ret[group][host] = hostvars[host]['ansible_eth0']['ipv4']['address']
return json.dumps(ret)
Which would emit a data structure similar to:
{"webservers":{"web0":"1.2.3.4","web1":"1.2.3.5"},{"caches":{"cache0":"1.2.3.6"}}}
However, I don't know how to build and pass this data structure to my jinja2 template. I really want to be able to create that datastructure and just put {{ grouped_hosts()|to_nice_json }} and call it a day. But how do I write, and where do I put, that grouped_hosts() function?
I'm not sure what you're trying to create with your template, but if you just want to output this as a json structure, you can do it this way :
{
{% set gdelim = '' %}
{% for group in groups %}
{{ gdelim }}"{{group}}": {
{% set hdelim = '' %}
{% for host in groups[group] %}
{{ hdelim }}"{{ host }}": "{{hostvars[host]['ansible_eth0']['ipv4']['address']}}"
{% set hdelim = ',' %}
{% endfor %}
}
{% set gdelim = ',' %}
{% endfor %}
}
The gdelim and hdelim are here to set the delimiter when required (note the delimiters prefixing objects).
At first run, the delimiter is empty, and then ",". Since the objects are prefixed by the delimiter, you don't have trailing comas, and thus the resulting JSON is valid (but a bit ugly).

Liquid: Can I get a random element from an Array?

I'm trying to pick a random element from an array -- is this possible using Liquid/Jekyll?
I can create an array -- and access a given index ... but is there a way to "shuffle" the array and then select an index, and thus get a random element from the array?
prefix: ["Foo", "Bar", "Baz"]
---
{{ page.prefix[1] }}
# outputs "Bar"
The 2018 answer is
{% assign prefix = page.prefix | sample: 2 %}
{{ prefix[0] }}
As the OP asked about Jekyll, this can be found at: https://jekyllrb.com/docs/templates/
Liquid doesn't have a filter for picking a random element from an array or an integer interval.
If you want Jekyll to do that, you would have to create an extension to add that liquid filter.
However, I must point out that doing so would pick a random element every time the page is generated, but not every time the page is viewed.
If you want to get different random values every time you visit a page, your best option is using javascript and letting the client pick a random value. You can use liquid to generate the relevant javascript though.
You may be able to do that just in Liquid, but it could less of generic solution like the one provided by #Brendan. According to this article, you can generate a random liquid number between min & max. So simply:
Assign the min to 0 and max to your array's length.
Loop over the array till you find your random number and pick you element.
Here is an example, get your random array index:
{% assign min = 0 %}
{% assign max = prefix.size %}
{% assign diff = max | minus: min %}
{% assign randomNumber = "now" | date: "%N" | modulo: diff | plus: min %}
Then find your random value:
{{ prefix[randomNumber] }}
You can create a plugin to get a random element. Something like this:
module Jekyll
module RandomFilter
# Use sample to get a random value from an array
#
# input - The Array to sample.
#
# Examples
#
# random([1, 2, 3, 4, 5])
# # => ([2])
#
# Returns a randomly-selected item out of an array.
def random(input)
input.sample(1)
end
end
end
Liquid::Template.register_filter(Jekyll::RandomFilter)
Then do something like this in your template to implement:
{% assign myArray = '1|2|3|4|5 | split: '|' %}
{% assign myNumber = myArray | random %}
Without using a plugin (which might be a requirement if you are using github pages for example) and don't want the choice to be set only at build/rebuild time.
This uses collections as it's data source and some feature flags set in the page front matter.
{% if page.announcements %}
<script>
// homepage callout
var taglines=[
{% for txt in site.announcements %}
{{ txt.content | markdownify | jsonify | append: "," }}
{% endfor %}
]
var selection = document.querySelector('#tagline') !== null;
if(selection) {
document.querySelector('#tagline').innerHTML = taglines[ Math.floor(Math.random()*taglines.length) ];
}
</script>
{% endif %}
I use markdownify to process the content, jsonify to make it JavaScript safe and then append a comma to make my array.
The Javascript then populates one randomly at page load.
Add collection to config.yml
collections:
announcements:
Add flag to page
---
layout: home
title:
slider: true
announcements: true
---
collection content item (test.md)
---
published: true
---
This is a test post
You could adapt Liquid::Drop and whitelist Ruby's sample method.
See https://github.com/Shopify/liquid/blob/master/lib/liquid/drop.rb#L69:
You would need to change:
blacklist -= [:sort, :count, :first, :min, :max, :include?]
to:
blacklist -= [:sort, :count, :first, :min, :max, :include?, :sample]
Next you could just use:
{{ some_liquid_array.sample }}