Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NOISSUE - Add Country, City, Service and Version filters to the UI #20

Merged
merged 18 commits into from
Aug 8, 2023
1 change: 1 addition & 0 deletions api/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ func retrieveSummaryEndpoint(svc callhome.Service) endpoint.Endpoint {
}
return telemetrySummaryRes{
Countries: summary.Countries,
Cities: summary.Cities,
TotalDeployments: summary.TotalDeployments,
}, nil
}
Expand Down
1 change: 1 addition & 0 deletions api/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ func (res uiRes) Headers() map[string]string {

type telemetrySummaryRes struct {
Musilah marked this conversation as resolved.
Show resolved Hide resolved
Countries []callhome.CountrySummary `json:"countries,omitempty"`
Cities []string `json:"cities,omitempty"`
TotalDeployments int `json:"total_deployments,omitempty"`
}

Expand Down
38 changes: 24 additions & 14 deletions service.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,18 @@ func (ts *telemetryService) Save(ctx context.Context, t Telemetry) error {
}

func (ts *telemetryService) RetrieveSummary(ctx context.Context, filters TelemetryFilters) (TelemetrySummary, error) {
return ts.repo.RetrieveDistinctIPsCountries(ctx, filters)
return ts.repo.RetrieveSummary(ctx, filters)
}

// ServeUI gets the callhome index html page
func (ts *telemetryService) ServeUI(ctx context.Context, filters TelemetryFilters) ([]byte, error) {
tmpl := template.Must(template.ParseFiles("./web/template/index.html"))

summary, err := ts.repo.RetrieveDistinctIPsCountries(ctx, filters)
summary, err := ts.repo.RetrieveSummary(ctx, filters)
if err != nil {
return nil, err
}
unfilteredSummary, err := ts.repo.RetrieveSummary(ctx, TelemetryFilters{})
if err != nil {
return nil, err
}
Expand All @@ -81,6 +85,7 @@ func (ts *telemetryService) ServeUI(ctx context.Context, filters TelemetryFilter
if err != nil {
return nil, err
}

Musilah marked this conversation as resolved.
Show resolved Hide resolved
var from, to string
if !filters.From.IsZero() {
from = filters.From.Format(time.DateOnly)
Expand All @@ -89,19 +94,24 @@ func (ts *telemetryService) ServeUI(ctx context.Context, filters TelemetryFilter
to = filters.To.Format(time.DateOnly)
}
data := struct {
Countries string
NoDeployments int
NoCountries int
MapData string
From string
To string
Countries string
Cities string
FilterCountries []CountrySummary
FilterCities []string
NoDeployments int
NoCountries int
MapData string
From string
To string
}{
Countries: string(countries),
NoDeployments: summary.TotalDeployments,
NoCountries: len(summary.Countries),
MapData: string(pg),
From: from,
To: to,
Countries: string(countries),
FilterCountries: unfilteredSummary.Countries,
FilterCities: unfilteredSummary.Cities,
NoDeployments: summary.TotalDeployments,
NoCountries: len(summary.Countries),
MapData: string(pg),
From: from,
To: to,
}

var res bytes.Buffer
Expand Down
5 changes: 3 additions & 2 deletions telemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type CountrySummary struct {

type TelemetrySummary struct {
Countries []CountrySummary `json:"countries,omitempty"`
Cities []string `json:"cities,omitempty"`
TotalDeployments int `json:"total_deployments,omitempty"`
}

Expand All @@ -58,6 +59,6 @@ type TelemetryRepo interface {

// RetrieveAll retrieves all telemetry events.
RetrieveAll(ctx context.Context, pm PageMetadata, filters TelemetryFilters) (TelemetryPage, error)
// RetrieveDistinctIPsCOuntries gets distinct ip addresses and countries from database.
RetrieveDistinctIPsCountries(ctx context.Context, filters TelemetryFilters) (TelemetrySummary, error)
// RetrieveSummary gets distinct countries and cities in a summarised form.
Musilah marked this conversation as resolved.
Show resolved Hide resolved
RetrieveSummary(ctx context.Context, filters TelemetryFilters) (TelemetrySummary, error)
}
4 changes: 2 additions & 2 deletions timescale/mocks/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ func (mr *mockRepo) Save(ctx context.Context, t callhome.Telemetry) error {
return r0
}

// RetrieveDistinctIPsCountries retrieve distinct
func (*mockRepo) RetrieveDistinctIPsCountries(ctx context.Context, filter callhome.TelemetryFilters) (callhome.TelemetrySummary, error) {
// RetrieveSummary retrieve distinct
Musilah marked this conversation as resolved.
Show resolved Hide resolved
func (*mockRepo) RetrieveSummary(ctx context.Context, filter callhome.TelemetryFilters) (callhome.TelemetrySummary, error) {
return callhome.TelemetrySummary{}, nil
}

Expand Down
20 changes: 17 additions & 3 deletions timescale/timescale.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,16 +126,16 @@ func (r repo) Save(ctx context.Context, t callhome.Telemetry) error {

}

// RetrieveDistinctIPsCountries retrieve distinct
func (r repo) RetrieveDistinctIPsCountries(ctx context.Context, filters callhome.TelemetryFilters) (callhome.TelemetrySummary, error) {
// RetrieveSummary retrieve distinct
Musilah marked this conversation as resolved.
Show resolved Hide resolved
func (r repo) RetrieveSummary(ctx context.Context, filters callhome.TelemetryFilters) (callhome.TelemetrySummary, error) {
filterQuery, params := generateQuery(filters)
var summary callhome.TelemetrySummary
q := fmt.Sprintf(`select count(distinct ip_address), country from telemetry %s group by country;`, filterQuery)
rows, err := r.db.NamedQuery(q, params)
if err != nil {
return callhome.TelemetrySummary{}, err
}
defer rows.Close()
var summary callhome.TelemetrySummary
for rows.Next() {
var val callhome.CountrySummary
if err := rows.StructScan(&val); err != nil {
Expand All @@ -146,6 +146,20 @@ func (r repo) RetrieveDistinctIPsCountries(ctx context.Context, filters callhome
for _, country := range summary.Countries {
summary.TotalDeployments += country.NoDeployments
}

q1 := fmt.Sprintf(`select distinct city from telemetry %s;`, filterQuery)
cityRows, err := r.db.NamedQuery(q1, params)
if err != nil {
return callhome.TelemetrySummary{}, err
}
defer cityRows.Close()
for cityRows.Next() {
var val string
if err := cityRows.Scan(&val); err != nil {
return callhome.TelemetrySummary{}, err
}
summary.Cities = append(summary.Cities, val)
}
return summary, nil
}

Expand Down
10 changes: 5 additions & 5 deletions timescale/tracing/tracing.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (

const (
retrieveAllOp = "retrieve_all_op"
retrieveDistinctIPsCountriesOp = "retrieve_distinct_IP_countries_op"
retrieveSummaryCountriesOp = "retrieve_distinct_IP_countries_op"
Musilah marked this conversation as resolved.
Show resolved Hide resolved
saveOp = "save_op"
)

Expand All @@ -35,11 +35,11 @@ func (rt *repoTracer) RetrieveAll(ctx context.Context, pm callhome.PageMetadata,
return rt.repo.RetrieveAll(ctx, pm, filter)
}

// RetrieveDistinctIPsCountries adds tracing middleware to retrieve distinct ips countries method.
func (rt *repoTracer) RetrieveDistinctIPsCountries(ctx context.Context, filter callhome.TelemetryFilters) (callhome.TelemetrySummary, error) {
ctx, span := rt.tracer.Start(ctx, retrieveDistinctIPsCountriesOp)
// RetrieveSummary adds tracing middleware to retrieve distinct ips countries method.
func (rt *repoTracer) RetrieveSummary(ctx context.Context, filter callhome.TelemetryFilters) (callhome.TelemetrySummary, error) {
ctx, span := rt.tracer.Start(ctx, retrieveSummaryCountriesOp)
defer span.End()
return rt.repo.RetrieveDistinctIPsCountries(ctx, filter)
return rt.repo.RetrieveSummary(ctx, filter)
}

// Save adds tracing middleware to save method.
Expand Down
22 changes: 19 additions & 3 deletions web/static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,25 @@ body {
flex-grow: 1;
height: 95%;
Musilah marked this conversation as resolved.
Show resolved Hide resolved
width: 100%;

}

.btn {
margin-left: 20px;
margin-right: 20px;
padding: 5px 20px;
text-align: center;
line-height: 10px;
}

#map {
height: 100%;
height: 500px;
width: 100%;
position: relative;
Musilah marked this conversation as resolved.
Show resolved Hide resolved
}
#filter-container {
display: flex;
justify-content: center;
left: 20px;
margin-bottom: 10px;
margin-top: 10px;
height: 5%;
Expand All @@ -39,10 +49,16 @@ body {
display: flex;
gap: 20px;
}
#myButton {
position: absolute;
top: 20px;
right: 20px;
z-index: 1000; /* Adjust the z-index as needed */
Musilah marked this conversation as resolved.
Show resolved Hide resolved
}

#from-date,
#to-date {
width: 200px;
width: 150px;
height: 30px;
border: 1px solid #ccc;
border-radius: 4px;
Expand Down
67 changes: 52 additions & 15 deletions web/template/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
<meta charset="utf-8" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.5.1/MarkerCluster.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.5.1/MarkerCluster.Default.css" />
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9" crossorigin="anonymous">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
Musilah marked this conversation as resolved.
Show resolved Hide resolved
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<link rel="stylesheet" href="/style.css"/>
<script src="https://unpkg.com/[email protected]/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.5.1/leaflet.markercluster.js"></script>
Expand All @@ -25,13 +25,51 @@ <h2>Mainflux Deployment Summary</h2>
</div>
<div class="container">
<div id="filter-container">
<form id="filter-form" onsubmit="applyFilter(event)">
<label for="from-date">From:</label>
<input type="date" id="from-date" name="from-date" value="{{.From}}">
<label for="to-date">To:</label>
<input type="date" id="to-date" name="to-date" value="{{.To}}">
<input class="btn btn-primary" type="submit" value="Apply">
</form>
<!-- Button trigger modal -->
<button type="button" class="btn btn-primary" id="myButton" data-bs-toggle="modal" data-bs-target="#applyFilters">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-filter" viewBox="0 0 16 16">
<path d="M6 10.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"/>
</svg>
</button>
<!-- Modal -->
<div class="modal fade" id="applyFilters" tabindex="-1" aria-labelledby="applyFiltersLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="applyFiltersLabel">Apply Filters</h1>
</div>
<div class="modal-body">
Musilah marked this conversation as resolved.
Show resolved Hide resolved
<div class="mb-3">
<form id="country-filter-form" onsubmit="applyFilter(event)">
Musilah marked this conversation as resolved.
Show resolved Hide resolved
<div>
Musilah marked this conversation as resolved.
Show resolved Hide resolved
<label for="from-date">From:</label>
<input type="date" id="from-date" name="from-date" value="{{.From}}">
<label for="to-date">To:</label>
<input type="date" id="to-date" name="to-date" value="{{.To}}">
</div>
Musilah marked this conversation as resolved.
Show resolved Hide resolved
<label for="country-filter" class="form-label">Country</label>
<select id="country-filter" class="form-select">
<option value="">Select a country</option>
{{range $i, $country := .FilterCountries}}
<option value="{{$country.Country}}">{{$country.Country}}</option>
{{end}}
</select>
<label for="city-filter" class="form-label">City</label>
<select id="city-filter" class="form-select">
<option value="">Select a city</option>
{{range $i, $city := .FilterCities}}
<option value="{{$city}}">{{$city}}</option>
{{end}}
</select>
<button type="submit" class="btn btn-primary">Apply</button>

</form>

</div>
</div>
</div>
</div>
</div>
</div>
<div id="error-message" class="error-message"></div>
Musilah marked this conversation as resolved.
Show resolved Hide resolved
<div class="main-content">
Expand All @@ -48,10 +86,10 @@ <h2>Mainflux Deployment Summary</h2>
const errorMessage = document.getElementById("error-message");
var fromDateInput = document.getElementById('from-date').value;
var toDateInput = document.getElementById('to-date').value;

var selectedCountry = document.getElementById('country-filter').value;
var selectedCity = document.getElementById('city-filter').value
var fromDate = fromDateInput ? new Date(fromDateInput) : null;
var toDate = toDateInput ? new Date(toDateInput) : null;

var toDate = toDateInput ? new Date(toDateInput) : null;
if (fromDate && toDate && fromDate > toDate) {
errorMessage.textContent = 'Date range is not valid!';
return;
Expand All @@ -65,7 +103,7 @@ <h2>Mainflux Deployment Summary</h2>
var fromDate = fromDateInput ? new Date(fromDateInput).toISOString() : '';
var toDate = toDateInput ? new Date(toDateInput).toISOString() : '';

var url = `/?from=${encodeURIComponent(fromDate)}&to=${encodeURIComponent(toDate)}`;
var url = `/?from=${encodeURIComponent(fromDate)}&to=${encodeURIComponent(toDate)}&country=${encodeURIComponent(selectedCountry)}&city=${encodeURIComponent(selectedCity)}`;
window.location.href = url;
}

Expand All @@ -89,7 +127,6 @@ <h2>Mainflux Deployment Summary</h2>
});
countryList.appendChild(listItem);
});

// Function to retrieve coordinates for a given country using Nominatim API
function getCountryCoordinates(country, callback) {
var url = 'https://nominatim.openstreetmap.org/search?format=json&q=' + encodeURIComponent(country);
Expand All @@ -111,7 +148,7 @@ <h2>Mainflux Deployment Summary</h2>
console.log('Error retrieving coordinates:', error);
});
}

function logJSONData() {
var mapData = `{{.MapData}}`;
const obj = JSON.parse(mapData);
Expand Down Expand Up @@ -140,8 +177,8 @@ <h2>Mainflux Deployment Summary</h2>
map.addLayer(countryMarkers);
});
}

logJSONData();
</script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm" crossorigin="anonymous"></script>
</body>
</html>