Skip to content

Commit

Permalink
Add support for custom columns (endoflife-date#4002)
Browse files Browse the repository at this point in the history
They can be used for documenting things such as related runtime versions, custom dates that cannot
be expressed using the default columns, etc. See the documentation in the contribution guide for
more information on how to use them.

This is only an initial support and:

- The allowed positions have been limited to positions that have a chance to be used. More positions
  may be added in the future if needed.
- Those properties may also be exposed in the new API responses (after endoflife-date#2080).

Closes endoflife-date#3642.
  • Loading branch information
marcwrobel committed Nov 5, 2023
1 parent c671e62 commit 7489fc2
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 3 deletions.
38 changes: 38 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,44 @@ extendedSupportColumn: Extended Support
# that the extended support date is approaching (optional, default = 121 days).
extendedSupportWarnThreshold: 121

# Custom columns configuration (optional).
# Custom columns are columns that will be added to the releases table and populated based on a
# custom release cycle property.
# They can be used for documenting things such as related runtime versions, custom dates that
# cannot be expressed using the default columns, etc.
# Note that the use of a table include (see https://github.com/endoflife-date/endoflife.date/blob/master/products/ansible.md)
# is usually preferred when there is more than two or three custom columns.
customColumns:

# Name of the custom property in release cycles (mandatory).
# If the release cycle does not declare this property, the label 'N/A' will be displayed instead.
# Custom properties follows the camel-case syntax for naming.
- property: supportedIosVersions

# Position of the custom column in the table (mandatory).
# Allowed values are:
# - after-release-column: this is typically used for documenting related runtime versions
# (such as the supported iOS version range for iphone models),
# - before-latest-column: this is typically used for documenting additional dates
# (such as an obsolescence date for an iphone model - https://support.apple.com/en-us/HT201624),
# - after-latest-column: this is typically used for documenting a corresponding latest version
# number (such as the OpenJDK version for https://endoflife.date/azul-zulu).
# If multiple columns have the same position, the order of the column in the customColumns list
# will be respected.
position: after-release-column

# Label of the custom column (mandatory).
# It will be displayed in the table header.
label: iOS

# A description of what contains the custom column (optional).
# It will be displayed as a tooltip of the column table header cell.
description: Supported iOS versions

# A link that gives more information about what contains the custom column (optional).
# It will be used to transform the table label to a link.
link: https://en.wikipedia.org/wiki/IPhone#Models

# Auto-update release configuration (optional).
# This is used for automatically updating `releaseDate`, `latest`, and `latestReleaseDate` for every release.
# Multiple configurations are allowed.
Expand Down
1 change: 1 addition & 0 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ defaults:
extendedSupportColumn: false
extendedSupportColumnLabel: 'Extended Support'
extendedSupportWarnThreshold: 121
customColumns: []
LTSLabel: '<abbr title="Long Term Support">LTS</abbr>'


Expand Down
12 changes: 12 additions & 0 deletions _includes/custom-column-td.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{%- comment %}
Render a product custom column data cell (<td>).

Parameters:
- release (mandatory): a product release cycle definition.
- column (mandatory): the custom column definition.
- cssClasses (optional): a space-separated list of CSS classes to add to the cell.
{% endcomment %}
{%- assign release = include.release %}
{%- assign propertyName = include.column.property %}
{%- assign cssClasses = include.cssClasses | default:'' %}
<td class="{{ cssClasses }}">{{ release[propertyName] | default: 'N/A' }}</td>
12 changes: 12 additions & 0 deletions _includes/custom-column-th.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{%- comment %}
Render a product custom column header cell (<th>).

Parameters:
- column (mandatory): the custom column definition.
{% endcomment %}
{%- assign label = include.column.label %}
{%- assign description = include.column.description %}
{%- assign link = include.column.link %}
<th title="{{ description }}">
{% if link %}<a href="{{ link }}">{{ label }}</a>{% else %}{{ label }}{% endif %}
</th>
22 changes: 20 additions & 2 deletions _layouts/product.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,24 @@ <h1>{{ page.title }}</h1>
<img alt="Release Schedule Image Gantt Chart for {{page.title}}" src="{{page.releaseImage}}" />
{% endif %}

{% capture now %}{{ "now" | date: "%s" | plus:0 }}{% endcapture %}
{%- capture now %}{{ "now" | date: "%s" | plus:0 }}{% endcapture %}
{%- assign customColumnsAfterRelease = page.customColumns | where: 'position', 'after-release-column' %}
{%- assign customColumnsBeforeLatest = page.customColumns | where: 'position', 'before-latest-column' %}
{%- assign customColumnsAfterLatest = page.customColumns | where: 'position', 'after-latest-column' %}

<table class="lifecycle">
<thead>
<tr>
<th>Release</th>{% assign colCount = 1 %}
{% for column in customColumnsAfterRelease %}{% include custom-column-th.html column=column %}{% assign colCount = colCount | plus:1 %}{% endfor %}
{% if page.releaseDateColumn %}<th>{{ page.releaseDateColumnLabel }}</th>{% assign colCount = colCount | plus:1 %}{% endif %}
{% if page.discontinuedColumn %}<th>{{ page.discontinuedColumnLabel }}</th>{% assign colCount = colCount | plus:1 %}{% endif %}
{% if page.activeSupportColumn %}<th>{{ page.activeSupportColumnLabel }}</th>{% assign colCount = colCount | plus:1 %}{% endif %}
{% if page.eolColumn %}<th>{{ page.eolColumnLabel }}</th>{% assign colCount = colCount | plus:1 %}{% endif %}
{% if page.extendedSupportColumn %}<th>{{ page.extendedSupportColumnLabel }}</th>{% assign colCount = colCount | plus:1 %}{% endif %}
{% for column in customColumnsBeforeLatest %}{% include custom-column-th.html column=column %}{% assign colCount = colCount | plus:1 %}{% endfor %}
{% if page.releaseColumn %}<th>{{ page.releaseColumnLabel }}</th>{% assign colCount = colCount | plus:1 %}{% endif %}
{% for column in customColumnsAfterLatest %}{% include custom-column-th.html column=column %}{% assign colCount = colCount | plus:1 %}{% endfor %}
</tr>
</thead>

Expand All @@ -49,7 +55,7 @@ <h1>{{ page.title }}</h1>
{%- if r.can_be_hidden %}{% assign releaseClasses = 'release can-be-hidden' %}{% endif %}
<tr class="{{ releaseClasses }}">
{%- assign cycleColumnClass = '' %}
{%- if r.daysTowardEol < 0 %}{% assign cycleColumnClass = 'txt-linethrough' %}{% endif %}
{%- if r.days_toward_eol < 0 %}{% assign cycleColumnClass = 'txt-linethrough' %}{% endif %}
<td class="{{ cycleColumnClass }}">
{% comment %}Only put a link in the version column if the release column is not shown{% endcomment %}
{% if page.releaseColumn == false and r.link %}
Expand All @@ -59,6 +65,10 @@ <h1>{{ page.title }}</h1>
{% endif %}
</td>

{%- for column in customColumnsAfterRelease %}
{% include custom-column-td.html release=r column=column cssClasses=cycleColumnClass %}
{%- endfor %}

{% if page.releaseDateColumn %}
<td>{{ r.releaseDate | timeago }} <div>({{ r.releaseDate | date_to_string }})</div></td>
{% endif %}
Expand Down Expand Up @@ -119,6 +129,10 @@ <h1>{{ page.title }}</h1>
</td>
{% endif %}

{%- for column in customColumnsBeforeLatest %}
{% include custom-column-td.html release=r column=column %}
{%- endfor %}

{% if page.releaseColumn != false %}
{%- assign releaseColumnClass = '' %}
{%- if r.days_toward_eol < 0 %}{% assign releaseColumnClass = 'txt-linethrough' %}{% endif %}
Expand All @@ -131,6 +145,10 @@ <h1>{{ page.title }}</h1>
{% if r.latestReleaseDate %}<div>({{ r.latestReleaseDate | date_to_string }})</div>{% endif %}
</td>
{% endif %}

{%- for column in customColumnsAfterLatest %}
{% include custom-column-td.html release=r column=column cssClasses=releaseColumnClass %}
{%- endfor %}
</tr>
{% endfor %}
{% assign can_be_hidden_releases_count = page.releases | where: 'can_be_hidden', true | size %}
Expand Down
23 changes: 22 additions & 1 deletion _plugins/product-data-validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module EndOfLifeHooks
VERSION = '1.0.0'
TOPIC = 'Product Validator:'
VALID_CATEGORIES = %w[app db device framework lang library os server-app service standard]
VALID_CUSTOM_COLUMN_POSITIONS = %w[after-release-column before-latest-column after-latest-column]

IGNORED_URL_PREFIXES = {
'https://www.nokia.com': 'always return a Net::ReadTimeout',
Expand Down Expand Up @@ -120,6 +121,15 @@ def self.validate(product)
error_if.is_not_an_array('identifiers')
error_if.is_not_an_array('releases')

product.data['customColumns'].each { |column|
error_if = Validator.new(product, column)
error_if.is_not_a_string('property')
error_if.is_not_in('position', EndOfLifeHooks::VALID_CUSTOM_COLUMN_POSITIONS)
error_if.is_not_a_string('label')
error_if.is_not_a_string('description') if column.has_key?('description')
error_if.is_not_an_url('link') if column.has_key?('link')
}

product.data['releases'].each { |release|
error_if = Validator.new(product, release)
error_if.is_not_a_string('releaseCycle')
Expand Down Expand Up @@ -151,6 +161,11 @@ def self.validate_urls(product)
error_if.is_url_invalid('iconUrl') if product.data['iconUrl']
error_if.contains_invalid_urls(product.content)

product.data['customColumns'].each { |column|
error_if = Validator.new(product, column)
error_if.is_url_invalid('link') if column['link']
}

product.data['releases'].each { |release|
error_if = Validator.new(product, release)
error_if.is_url_invalid('link') if release['link']
Expand Down Expand Up @@ -311,7 +326,13 @@ def declare_error(property, value, details)
end

def location
@data.has_key?('releaseCycle') ? "#{@product.name}##{@data['releaseCycle']}" : @product.name
if @data.has_key?('releaseCycle')
"#{@product.name}#releases##{@data['releaseCycle']}"
elsif @data.has_key?('property')
"#{@product.name}#customColumn##{@data['property']}"
else
@product.name
end
end
end
end
Expand Down
51 changes: 51 additions & 0 deletions product-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,14 @@
]
}
},
"customColumns": {
"title": "Custom columns",
"description": "Array of custom columns for this product.",
"type": "array",
"items": {
"$ref": "#/$defs/customColumn"
}
},
"releases": {
"title": "Releases",
"description": "Array of releases for this product.",
Expand Down Expand Up @@ -306,6 +314,49 @@
"description": "Regex which will be used to filter the releases.",
"type": "string"
},
"customColumn": {
"type": "object",
"properties": {
"property": {
"title": "Property",
"description": "Name of the custom property in release cycles.",
"examples": [
"supportedIosVersions",
"correspondingAndroidVersion"
],
"type": "string"
},
"position": {
"title": "Position",
"description": "Position of the custom column in the table.",
"enum": [
"after-release-column",
"before-latest-column",
"after-latest-column"
]
},
"label": {
"title": "Label",
"description": "Label of the custom column.",
"type": "string"
},
"description": {
"title": "Description",
"description": "A description of what contains the custom column.",
"type": "string"
},
"link": {
"title": "Link",
"description": "A link that gives more information about what contains the custom column.",
"type": "string"
}
},
"required": [
"property",
"position",
"label"
]
},
"release": {
"type": "object",
"properties": {
Expand Down

0 comments on commit 7489fc2

Please sign in to comment.