Skip to content

Commit

Permalink
Merge pull request #1559 from sanger/dpl-1028-group-graph-pipelines
Browse files Browse the repository at this point in the history
DPL-1059: Group graph pipelines
  • Loading branch information
StephenHulme authored Jan 26, 2024
2 parents 7501a04 + 29d8553 commit 072d67e
Show file tree
Hide file tree
Showing 9 changed files with 656 additions and 382 deletions.
14 changes: 14 additions & 0 deletions app/assets/stylesheets/limber/icons.scss
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,18 @@ $icon-size: 24px;
'<path fill-rule="evenodd" d="M11.03 3.97a.75.75 0 0 1 0 1.06l-6.22 6.22H21a.75.75 0 0 1 0 1.5H4.81l6.22 6.22a.75.75 0 1 1-1.06 1.06l-7.5-7.5a.75.75 0 0 1 0-1.06l7.5-7.5a.75.75 0 0 1 1.06 0Z" clip-rule="evenodd" />',
$theme-color
);

// rectangle-stack (solid)
@include icon(
'pipeline_stack',
'<path fill-rule="evenodd" d="M5.566 4.657A4.505 4.505 0 016.75 4.5h10.5c.41 0 .806.055 1.183.157A3 3 0 0015.75 3h-7.5a3 3 0 00-2.684 1.657ZM2.25 12a3 3 0 013-3h13.5a3 3 0 013 3v6a3 3 0 01-3 3H5.25a3 3 0 01-3-3v-6ZM5.25 7.5c-.41 0-.806.055-1.184.157A3 3 0 016.75 6h10.5a3 3 0 012.683 1.657A4.505 4.505 0 0018.75 7.5H5.25ZM12 10.85a.75.75 0 01.75.75v4.94l1.72-1.72a.75.75 0 111.06 1.06l-3 3a.75.75 0 01-1.06 0l-3-3a.75.75 0 111.06-1.06l1.72 1.72v-4.94a.75.75 0 01.75-.75Z" />',
$theme-color
);

// custom - modified from rectangle-stack (solid)
@include icon(
'pipeline_single',
'<path fill-rule="evenodd" d="M2.25 7a3 3 0 013-3h13.5a3 3 0 013 3v6a3 3 0 01-3 3H5.25a3 3 0 01-3-3v-6ZM12 5.85a.75.75 0 01.75.75v4.94l1.72-1.72a.75.75 0 111.06 1.06l-3 3a.75.75 0 01-1.06 0l-3-3a.75.75 0 111.06-1.06l1.72 1.72v-4.94a.75.75 0 01.75-.75Z" />',
$theme-color
);
}
21 changes: 21 additions & 0 deletions app/assets/stylesheets/limber/pipeline-graph.scss
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,24 @@
@extend .mr-1;
}

#show-pipeline-groups {
@extend .icon-pipeline_stack-light;
@extend .mr-1;
}
#show-pipeline-groups:hover {
@extend .icon-pipeline_single-light;
@extend .text-light:hover;
}

#show-pipelines {
@extend .icon-pipeline_single-light;
@extend .mr-1;
}
#show-pipelines:hover {
@extend .icon-pipeline_stack-light;
@extend .text-light:hover;
}

#pipelines-back {
@extend .icon-arrowleft-light;
@extend .ml-1;
Expand All @@ -64,6 +82,9 @@
background-size: 1.5em;
vertical-align: text-bottom;
}
#pipelines-back:hover {
@extend .text-light:hover;
}
}
ul {
@extend .list-group;
Expand Down
51 changes: 45 additions & 6 deletions app/controllers/pipelines_controller.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

# Provides an overview of the pipelines auto-generated from the configuration
# Currently still very quick and dirty
# Tailored to the needs of the pipeline graph
class PipelinesController < ApplicationController
before_action :configure_api, except: :index
def index
Expand All @@ -20,14 +20,53 @@ def calculate_pipelines
Settings.pipelines.map { |pl| { name: pl.name, filters: pl.filters } }
end

def calculate_edges
Settings.pipelines.flat_map do |pl|
pl.relationships.map do |s, t|
{ group: 'edges', data: { id: SecureRandom.uuid, source: s, target: t, pipeline: pl.name } }
end
def calculate_edges_with_pipeline_data
Settings.pipelines.flat_map { |pl| pl.relationships.map { |s, t| { source: s, target: t, pipeline: pl } } }
end

def calculate_pipeline_edges
calculate_edges_with_pipeline_data.map do |edge|
{
group: 'edges',
data: {
id: SecureRandom.uuid,
source: edge[:source],
target: edge[:target],
pipeline: edge[:pipeline].name
}
}
end
end

def calculate_group_edges
calculate_edges_with_pipeline_data
.map do |edge|
{
group: 'edges',
data: {
id: SecureRandom.uuid,
source: edge[:source],
target: edge[:target],
group: edge[:pipeline].pipeline_group
}
}
end
.uniq { |edge| edge[:data][:source] + edge[:data][:target] + edge[:data][:group] }
end

# Generate the edges for the graph, consisting of edges for the pipelines and the pipeline groups.
#
# The edges are a graphing construct rather than a pipeline construct. Combining pipeline and
# group properties into a single edge prevents easy toggling between pipelines and groups
# down-stream:
# In the case where we have 3 different pipelines between node A and node B this is represented by
# 3 pipeline edges A ⇶ B, but where we have these 3 different pipelines as part of the same group,
# in group view we only want to see a single edge A → B, not 3 identically coloured and labeled
# edges.
def calculate_edges
calculate_pipeline_edges + calculate_group_edges
end

def calculate_nodes
Settings.purposes.map do |_uuid, purpose|
{
Expand Down
117 changes: 117 additions & 0 deletions app/javascript/pipeline-graph/colourMapping.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// TODO: combine these colours with app/assets/stylesheets/limber/colours.scss into a single source

// First 96 of the distinct colours as used in the rest of limber
const distinctColours = [
'#FFFF00',
'#1CE6FF',
'#FF34FF',
'#FF4A46',
'#008941',
'#006FA6',
'#A30059',
'#FFDBE5',
'#7A4900',
'#0000A6',
'#63FFAC',
'#B79762',
'#004D43',
'#8FB0FF',
'#997D87',
'#5A0007',
'#809693',
'#FEFFE6',
'#1B4400',
'#4FC601',
'#3B5DFF',
'#4A3B53',
'#FF2F80',
'#61615A',
'#BA0900',
'#6B7900',
'#00C2A0',
'#FFAA92',
'#FF90C9',
'#B903AA',
'#D16100',
'#DDEFFF',
'#000035',
'#7B4F4B',
'#A1C299',
'#300018',
'#0AA6D8',
'#013349',
'#00846F',
'#372101',
'#FFB500',
'#C2FFED',
'#A079BF',
'#CC0744',
'#C0B9B2',
'#C2FF99',
'#001E09',
'#00489C',
'#6F0062',
'#0CBD66',
'#EEC3FF',
'#456D75',
'#B77B68',
'#7A87A1',
'#788D66',
'#885578',
'#FAD09F',
'#FF8A9A',
'#D157A0',
'#BEC459',
'#456648',
'#0086ED',
'#886F4C',
'#34362D',
'#B4A8BD',
'#00A6AA',
'#452C2C',
'#636375',
'#A3C8C9',
'#FF913F',
'#938A81',
'#575329',
'#00FECF',
'#B05B6F',
'#8CD0FF',
'#3B9700',
'#04F757',
'#C8A1A1',
'#1E6E00',
'#7900D7',
'#A77500',
'#6367A9',
'#A05837',
'#6B002C',
'#772600',
'#D790FF',
'#9B9700',
'#549E79',
'#FFF69F',
'#201625',
'#72418F',
'#BC23FF',
'#99ADC0',
'#3A2465',
'#922329',
'#5B4534',
]

let pipelineColours = {} // colours assigned to each pipeline name

const calculatePipelineColours = function (pipelineNames) {
const coloursCopy = [...distinctColours]
pipelineNames.forEach((pipeline) => {
const colour = coloursCopy.shift()
pipelineColours[pipeline] = colour
})
}

const getPipelineColour = function (pipeline) {
return pipelineColours[pipeline] || '#666'
}

export default { calculatePipelineColours, getPipelineColour }
44 changes: 37 additions & 7 deletions app/javascript/pipeline-graph/filterFunctions.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,58 @@
// This file contains functions for filtering the graph based on different criteria

let notResults = undefined

// maintain the existing filters so that individual fields can be updated as required
let existingFilter = '' // default filter (show all)
let existingShowPipelineGroups = true // default (show groups)

let filterHistory = [existingFilter] // start with the existing (empty) filter

const hasPreviousFilter = () => filterHistory.length > 0

const getPreviousFilter = () => {
filterHistory.pop() // remove the current filter
return filterHistory.pop() // return the previous filter
}

/**
* Searches for nodes and edges in a Cytoscape.js graph that match a given query.
* Nodes are matched if their 'id' attribute contains the query string.
* Edges are matched if their 'pipeline' attribute starts with the query string.
* Edges are matched if their 'pipeline' or 'group' attribute starts with the query string.
* The function also includes neighboring nodes of matched nodes and connected nodes of matched edges.
* As a side-effect, all non-matching elements are removed from the graph.
*
* @param {cytoscape.Core} cy - The Cytoscape instance (i.e., the graph).
* @param {string} query - The search term.
* @param {Object} filter - The filter. It has two properties:
* - term: The filter term.
* - showPipelineGroups: Whether to show pipeline groups.
* @returns {cytoscape.Collection} - A collection of nodes and edges that match the query.
*/
const findResults = (cy, query) => {
const findResults = (cy, filter) => {
if (notResults !== undefined) {
notResults.restore()
}

if (filter.term !== undefined && filter.term !== null)
if (filterHistory[filterHistory.length - 1] !== filter) {
// add filter term to (internal - not browser) history
// don't add duplicate filters
filterHistory.push(filter.term)
}

existingFilter = filter?.term ?? existingFilter
existingShowPipelineGroups = filter?.showPipelineGroups ?? existingShowPipelineGroups

const all = cy.$('*')
let results = cy.collection()

let purposes = cy.$(`node[id @*= "${query}"]`)
purposes = purposes.union(purposes.neighborhood())
let edgeType = existingShowPipelineGroups ? 'group' : 'pipeline'

let purposes = cy.$(`node[id @*= "${existingFilter}"]`)
purposes = purposes.union(purposes.neighborhood(`node, edge[${edgeType}]`))
results = results.union(purposes)

let pipelines = cy.$(`edge[pipeline @^= "${query}"]`)
let pipelines = cy.$(`edge[${edgeType}][${edgeType} @^= "${existingFilter}"]`)
pipelines = pipelines.union(pipelines.connectedNodes())
results = results.union(pipelines)

Expand All @@ -31,4 +61,4 @@ const findResults = (cy, query) => {
return results
}

export { findResults }
export default { hasPreviousFilter, getPreviousFilter, findResults }
Loading

0 comments on commit 072d67e

Please sign in to comment.