Skip to content

Commit

Permalink
Add StageType Filter for Stage Tabs in each Environment (#1718)
Browse files Browse the repository at this point in the history
* basic dropdown format

* Squash merge master into bnordemann/stages-ui-filter

* dropdown filled with stage types

* added filter and persist on tab navigation

* rename GET filter param for clarity

* styling changes

* make checkboxes bigger and align text better

* fix css spacing

* url params change on filter update

* changed filter param to stageFilter

* updated css styling

* merge from main

* added filter buttons on filter change for more visibility

* edited filter size and style

* fix erroneously changed files

* fix spacing

* fix end of file
  • Loading branch information
bhnord authored Sep 23, 2024
1 parent dffb362 commit abc2fae
Show file tree
Hide file tree
Showing 3 changed files with 293 additions and 11 deletions.
108 changes: 105 additions & 3 deletions deploy-board/deploy_board/static/css/deploy-board.css
Original file line number Diff line number Diff line change
Expand Up @@ -347,20 +347,122 @@ body {
display: inline-block;
border-style: solid;
border-radius: 25px;
border-width: 2px;
border-color: #ccc;
padding-left: 5px;
padding-right: 5px;
background-color: #fff;
}

.mr-1 {

.stage-filter-reset-button {
float: right;
margin-right: 5px;
border-style: none;
}

.checkbox-menu li label {
display: block;
padding: 2px 0;
padding-left: 10px;
font-weight: normal;
margin: 0;
}

.checkbox-menu li.active label {
background-color:#c8e3fa;
}

.checkbox-menu li.active label:hover {
background-color: #b8d0e6;
}

.checkbox-menu li label:hover {
background-color: #f5f5f5;
}

.checkbox-menu li input{
width: 17px;
height: 17px;
margin-right: 3px;
position: relative;
top: 2px;
}

.checkbox-menu li.active label {
font-weight: bold;
}

.checkbox-menu li span {
position: relative;
bottom: 2px;
}

.filter-wide {
margin-bottom: 0;
padding-left: 0;
list-style: none;
width: 100%;
position: relative;
}

.filter-wide > li {
position: relative;
display: block;
}

.filter-tabs {
margin-top: -1px;
}

.filter-tabs > li {
float: left;
margin-bottom: -1px;
}

.filter-tabs > li > button:hover {
border-color: #1c1e22 #1c1e22 #1c1e22;
}
.filter-tabs > li > button {
font-size: inherit;
}

#stageFilterButton {
font-size: inherit;
margin-right: 15px;
}

#stageFilterOptions {
width: 100%;
display: inline-block;
margin-bottom: 20px;
padding: 5px;
padding-right: 0px;
font-size: 12px;

border-bottom: 1px solid #ddd;
background-color: #f5f5f5;
}

#stageFilterDropdown {
margin-bottom: 4px;
display: block;
}

#stageTabsView {
margin-top: 5px;
border: 1px solid #ddd;
border-radius: 4px 4px 0 0;
}

.mr-1 {
margin-right: 5px;
}

.ml-1 {
margin-left: 5px;
margin-left: 5px;
}

.mt-1 {
margin-top: 5px;
margin-top: 5px;
}
190 changes: 182 additions & 8 deletions deploy-board/deploy_board/templates/environs/env_tabs.tmpl
Original file line number Diff line number Diff line change
@@ -1,8 +1,182 @@
<ul class="nav nav-tabs">
{% for stage in envs %}
<li {% if stage.stageName == env.stageName %}class="active"{% endif %}>
<a class="deployToolTip" data-container="body" data-toggle="tooltip" title="{{stage.description}}" data-placement="bottom"
href="/env/{{ env.envName }}/{{ stage.stageName }}/{{ envTabKind }}">{{ stage.stageName }}</a>
</li>
{% endfor %}
</ul>
<div id="stageTabsView">
<div id="stageFilterOptions">
<div id="stageFilterDropdown" class="dropdown" style="float:left">
<button
id="stageFilterButton"
class="deployToolTip dropdown-toggle btn btn-default"
type="button"
data-toggle="dropdown"
title="Filter environmnent stages by stage type">
Stage Type Filter
<span class="glyphicon glyphicon-chevron-down"/>
</button>
<ul class="dropdown-menu checkbox-menu allow-focus" >
{% for stage in all_stage_types %}
<li >
<label onclick="event.stopPropagation()">
<input data-stagetype="{{stage}}" type="checkbox"/>
<span>{{stage|title}}</span>
</label>
</li>
{% endfor %}
<div class="divider"></div>
<button class="btn btn-default stage-filter-reset-button" onclick="clearFilter()">Reset</button>
</ul>
</div>

<ul id="filterTabs" class="filter-wide filter-tabs">
<li id="activeFiltersLabel" style="margin-right: 10px;">
<span style="line-height: 30px;">Active Filters:</span>
</li>
{% for stage in all_stage_types %}
<li >
<button onclick="updateFilterSingle(this)" data-stagetype={{stage}} class="btn btn-default" style="display:none;">
{{stage|title}}
<span class="glyphicon glyphicon-remove"/>
</button>
</li>
{% endfor %}
</ul>
</div>
<ul id="stageTabs" class="nav nav-tabs" style="padding-right:40px;">
{% for stage in envs %}
<li data-stagetype="{{stage.stageType}}" {% if stage.stageName == env.stageName %}class="active"{% endif %}>
<a class="deployToolTip" data-container="body" data-toggle="tooltip" title="{{stage.description}}" data-placement="bottom"
onclick="callback(event, this)"
href="/env/{{ env.envName }}/{{ stage.stageName }}/{{ envTabKind }}">{{ stage.stageName }}</a>
</li>
{% endfor %}
</ul>
<div id="filterOpts" data-filter="{{stagetype_filter}}" />
</div>


<script>
let filter;
let filterInactive = true;

try {
filter = Array.from(JSON.parse(decodeURIComponent($("#filterOpts").attr("data-filter"))));
console.log(filter);
if(!Array.isArray(filter)){
throw new Error("invalid url filter params");
}
} catch {
filter = [];
}


$("#stageFilterDropdown").on("change", "input[type='checkbox']", function() {
$(this).closest("li").toggleClass("active", this.checked);


let stageType = $(this).attr("data-stagetype");
if(this.checked){
filter.push(stageType);
} else {
//remove from filter
let index = filter.indexOf(stageType);
if(index > -1){
filter.splice(index, 1);
}
}
update();
});

update();
function update() {
console.log(filter)
if(filter.length < 1){
filterInactive = true;
} else {
filterInactive = false;
}
updateChecklist();
updateFilterButtons();
runFilter();

if(filterInactive){
$("#activeFiltersLabel").css("display", "none")
} else {
$("#activeFiltersLabel").css("display", "block")
}
}

//set tabs visible by filter
function runFilter(){
$("#stageTabs").children().each(function() {
let stageType = $(this).attr("data-stagetype");
if(filter.includes(stageType) || filterInactive){
this.style.display = "block";
} else {
this.style.display = "none";
}
})

/// styling to button based on filter active or not
$("#stageFilterButton").toggleClass("btn-primary", !filterInactive);
updateUrl();
}

//uncheck all filter boxes and turn filter off
function clearFilter(){
filter=[];
update();
}

function updateChecklist(){
$('#stageFilterDropdown > ul').children().each(function() {
let k = $(this).find("input:first");
if(filter.includes(k.attr("data-stagetype"))){
k.prop("checked", true);
$(this).addClass("active");
} else {
k.prop("checked", false);
$(this).removeClass("active");
}
})
}

function updateFilterButtons() {
$('#filterTabs').children().each(function() {
let k = $(this).find("button:first");
if(filter.includes(k.attr("data-stagetype"))){
k.css("display", "block");
} else {
k.css("display", "none");
}
})

}

function updateFilterSingle(btn){
let stageType = $(btn).attr("data-stagetype");
let index = filter.indexOf(stageType);
if(index > -1){
filter.splice(index, 1);
}

update();
}

//update filter param in url as it changes
function updateUrl(){
let url = new URL(window.location.href);
if(filter.length < 1){
url.searchParams.delete("stageFilter");
} else {
url.searchParams.set("stageFilter", JSON.stringify(filter));
}
window.history.replaceState(null,null,url);
}

//intercept and add filter parameter to link
function callback(e, anchor){
if(!filterInactive){
e.preventDefault();
let url = new URL(anchor.href);
url.searchParams.set("stageFilter", JSON.stringify(filter));
window.location.href=url;
}
}
</script>
6 changes: 6 additions & 0 deletions deploy-board/deploy_board/webapp/env_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,13 +444,17 @@ def get(self, request, name, stage=None):

asg_suspended_processes = _get_asg_suspended_processes(request, env) or []

stagetype_filter = request.GET.get('stageFilter', [])

if not env['deployId']:
capacity_hosts = deploys_helper.get_missing_hosts(request, name, stage)
provisioning_hosts = deduplicate_hosts(environ_hosts_helper.get_hosts(request, name, stage))

response = render(request, 'environs/env_landing.html', {
"envs": envs,
"env": env,
"all_stage_types": sorted(environs_helper.STAGE_TYPES),
"stagetype_filter": stagetype_filter,
"env_promote": env_promote,
"stages": stages,
"metrics": metrics,
Expand Down Expand Up @@ -546,6 +550,8 @@ def get(self, request, name, stage=None):
context = {
"envs": envs,
"env": env,
"all_stage_types": sorted(environs_helper.STAGE_TYPES),
"stagetype_filter": stagetype_filter,
"env_promote": env_promote,
"stages": stages,
"report": report,
Expand Down

0 comments on commit abc2fae

Please sign in to comment.