Skip to content

Commit

Permalink
improve search UI
Browse files Browse the repository at this point in the history
  • Loading branch information
its-felix committed May 5, 2024
1 parent 1e6dfdd commit cdc2777
Show file tree
Hide file tree
Showing 13 changed files with 494 additions and 169 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
- name: 'Build'
working-directory: 'go/api'
run: |
go build --ldflags '-extldflags "-static"' -o bootstrap
go build --ldflags '-extldflags "-static"' -o bootstrap -tags "lambda"
- name: 'Store API artifact'
uses: actions/upload-artifact@v4
with:
Expand Down
33 changes: 33 additions & 0 deletions go/api/config_lambda.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//go:build lambda

package main

import (
"cmp"
"context"
"errors"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/explore-flights/monorepo/go/api/search"
"os"
"strconv"
)

func echoPort() int {
port, _ = strconv.Atoi(os.Getenv("AWS_LWA_PORT"))
return cmp.Or(port, 8080)
}

func flightRepo(ctx context.Context) (*search.FlightRepo, error) {
dataBucket := os.Getenv("FLIGHTS_DATA_BUCKET")
if dataBucket == "" {
return nil, errors.New("env variable FLIGHTS_DATA_BUCKET required")
}

cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
return nil, err
}

return search.NewFlightRepo(s3.NewFromConfig(cfg), dataBucket), nil
}
27 changes: 27 additions & 0 deletions go/api/config_local.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//go:build !lambda

package main

import (
"context"
"github.com/explore-flights/monorepo/go/api/local"
"github.com/explore-flights/monorepo/go/api/search"
"os"
"path/filepath"
)

func echoPort() int {
return 8080
}

func flightRepo(ctx context.Context) (*search.FlightRepo, error) {
home, err := os.UserHomeDir()
if err != nil {
return nil, err
}

return search.NewFlightRepo(
local.NewS3Client(filepath.Join(home, "Downloads", "local_s3")),
"flights_data_bucket",
), nil
}
27 changes: 27 additions & 0 deletions go/api/local/s3.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//go:build !lambda

package local

import (
"context"
"github.com/aws/aws-sdk-go-v2/service/s3"
"os"
"path/filepath"
)

type S3Client struct {
basePath string
}

func NewS3Client(basePath string) *S3Client {
return &S3Client{basePath}
}

func (s3c *S3Client) GetObject(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) {
f, err := os.Open(filepath.Join(s3c.basePath, *params.Bucket, *params.Key))
if err != nil {
return nil, err
}

return &s3.GetObjectOutput{Body: f}, nil
}
30 changes: 4 additions & 26 deletions go/api/main.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
package main

import (
"cmp"
"context"
"errors"
"fmt"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/explore-flights/monorepo/go/api/search"
"github.com/goccy/go-graphviz"
"github.com/labstack/echo/v4"
Expand All @@ -19,37 +16,18 @@ import (
"time"
)

var port int
var dataBucket string

func init() {
port, _ = strconv.Atoi(os.Getenv("AWS_LWA_PORT"))
port = cmp.Or(port, 8080)

dataBucket = os.Getenv("FLIGHTS_DATA_BUCKET")
if dataBucket == "" {
panic("env variable FLIGHTS_DATA_BUCKET required")
}
}

func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
defer stop()

cfg, err := config.LoadDefaultConfig(ctx)
fr, err := flightRepo(ctx)
if err != nil {
panic(err)
}

s3c := s3.NewFromConfig(cfg)
fr := search.NewFlightRepo(s3c, dataBucket)
handler := search.NewConnectionsHandler(fr)

e := echo.New()
e.GET("/api/hello", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello world!")
})

e.GET("/api/connections/:export", func(c echo.Context) error {
ctx := c.Request().Context()

Expand Down Expand Up @@ -132,8 +110,8 @@ func main() {
c.Response().WriteHeader(http.StatusOK)
return search.ExportConnectionsImage(c.Response(), conns, graphviz.PNG)

case "reactflow":
return c.JSON(http.StatusOK, search.ExportConnectionsReactflow(conns))
case "json":
return c.JSON(http.StatusOK, search.ExportConnectionsJson(conns))

default:
c.Response().Header().Set(echo.HeaderContentType, echo.MIMETextPlainCharsetUTF8)
Expand All @@ -158,7 +136,7 @@ func run(ctx context.Context, e *echo.Echo) error {
}
}()

if err := e.Start(fmt.Sprintf(":%d", port)); err != nil {
if err := e.Start(fmt.Sprintf(":%d", echoPort())); err != nil {
if errors.Is(err, http.ErrServerClosed) {
return nil
}
Expand Down
111 changes: 66 additions & 45 deletions go/api/search/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,32 @@ import (
"time"
)

type Reactflow struct {
Nodes []Node `json:"nodes"`
Edges []Edge `json:"edges"`
type ConnectionsResponse struct {
Connections []ConnectionResponse `json:"connections"`
Flights map[string]FlightResponse `json:"flights"`
}

type Node struct {
Id int `json:"id"`
X int `json:"x"`
Y int `json:"y"`
Label string `json:"label"`
type ConnectionResponse struct {
FlightId string `json:"flightId"`
Outgoing []ConnectionResponse `json:"outgoing"`
}

type Edge struct {
Source int `json:"source"`
Target int `json:"target"`
Label string `json:"label"`
type FlightResponse struct {
FlightNumber FlightNumberResponse `json:"flightNumber"`
DepartureTime time.Time `json:"departureTime"`
DepartureAirport string `json:"departureAirport"`
ArrivalTime time.Time `json:"arrivalTime"`
ArrivalAirport string `json:"arrivalAirport"`
AircraftOwner string `json:"aircraftOwner"`
AircraftType string `json:"aircraftType"`
Registration string `json:"registration,omitempty"`
CodeShares []FlightNumberResponse `json:"codeShares"`
}

type FlightNumberResponse struct {
Airline string `json:"airline"`
Number int `json:"number"`
Suffix string `json:"suffix,omitempty"`
}

func ExportConnectionsText(w io.Writer, conns []Connection) error {
Expand Down Expand Up @@ -120,46 +130,57 @@ func buildGraph(parent *common.Flight, conns []Connection, graph *cgraph.Graph,
return nil
}

func ExportConnectionsReactflow(conns []Connection) Reactflow {
var rf Reactflow
buildReactFlow(conns, &rf, 0, 0, nil, make(map[*common.Flight]int))
return rf
}
func ExportConnectionsJson(conns []Connection) ConnectionsResponse {
flights := make(map[string]FlightResponse)
connections := buildConnectionsResponse(conns, flights)

func buildReactFlow(conns []Connection, rf *Reactflow, id, y int, parent *common.Flight, lookup map[*common.Flight]int) int {
const xIncr = 200
const yIncr = 100
return ConnectionsResponse{
Connections: connections,
Flights: flights,
}
}

x := 0
func buildConnectionsResponse(conns []Connection, flights map[string]FlightResponse) []ConnectionResponse {
r := make([]ConnectionResponse, 0, len(conns))

for _, conn := range conns {
var nodeId int
var ok bool

if nodeId, ok = lookup[conn.Flight]; !ok {
nodeId = id
lookup[conn.Flight] = nodeId
rf.Nodes = append(rf.Nodes, Node{
Id: nodeId,
X: x,
Y: y,
Label: fmt.Sprintf("%s\n%s-%s\n%s", conn.Flight.Number().String(), conn.Flight.DepartureAirport, conn.Flight.ArrivalAirport, conn.Flight.AircraftType),
})

id++
flightId := fmt.Sprintf("%v@%v@%v", conn.Flight.Number(), conn.Flight.DepartureAirport, conn.Flight.DepartureDate())
if _, ok := flights[flightId]; !ok {
flights[flightId] = FlightResponse{
FlightNumber: FlightNumberResponse{
Airline: string(conn.Flight.Airline),
Number: conn.Flight.FlightNumber,
Suffix: conn.Flight.Suffix,
},
DepartureTime: conn.Flight.DepartureTime,
DepartureAirport: conn.Flight.DepartureAirport,
ArrivalTime: conn.Flight.ArrivalTime,
ArrivalAirport: conn.Flight.ArrivalAirport,
AircraftOwner: string(conn.Flight.AircraftOwner),
AircraftType: conn.Flight.AircraftType,
Registration: conn.Flight.Registration,
CodeShares: convertCodeShares(conn.Flight.CodeShares),
}
}

if parent != nil {
rf.Edges = append(rf.Edges, Edge{
Source: lookup[parent],
Target: nodeId,
Label: conn.Flight.DepartureTime.Sub(parent.ArrivalTime).String(),
})
}
r = append(r, ConnectionResponse{
FlightId: flightId,
Outgoing: buildConnectionsResponse(conn.Outgoing, flights),
})
}

return r
}

id = buildReactFlow(conn.Outgoing, rf, id, y+yIncr, conn.Flight, lookup)
x += xIncr
func convertCodeShares(inp []common.FlightNumber) []FlightNumberResponse {
r := make([]FlightNumberResponse, 0, len(inp))
for _, fn := range inp {
r = append(r, FlightNumberResponse{
Airline: string(fn.Airline),
Number: fn.Number,
Suffix: fn.Suffix,
})
}

return id
return r
}
41 changes: 9 additions & 32 deletions go/api/search/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,49 +6,26 @@ import (
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/explore-flights/monorepo/go/common"
"github.com/explore-flights/monorepo/go/common/concurrent"
"golang.org/x/sync/errgroup"
"sync"
)

type concurrentMap[K comparable, V any] struct {
m map[K]V
mtx *sync.RWMutex
}

func (cm concurrentMap[K, V]) Get(k K) (V, bool) {
cm.mtx.RLock()
defer cm.mtx.RUnlock()

v, ok := cm.m[k]
return v, ok
}

func (cm concurrentMap[K, V]) Set(k K, v V) {
cm.mtx.Lock()
defer cm.mtx.Unlock()
cm.m[k] = v
}

func (cm concurrentMap[K, V]) Del(k K) {
cm.mtx.Lock()
defer cm.mtx.Unlock()
delete(cm.m, k)
type MinimalS3Client interface {
GetObject(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error)
}

type FlightRepo struct {
s3c *s3.Client
s3c MinimalS3Client
bucket string
cache concurrentMap[common.LocalDate, []*common.Flight]
cache concurrent.Map[common.LocalDate, []*common.Flight]
}

func NewFlightRepo(s3c *s3.Client, bucket string) *FlightRepo {
func NewFlightRepo(s3c MinimalS3Client, bucket string) *FlightRepo {
return &FlightRepo{
s3c: s3c,
bucket: bucket,
cache: concurrentMap[common.LocalDate, []*common.Flight]{
m: make(map[common.LocalDate][]*common.Flight),
mtx: new(sync.RWMutex),
},
cache: concurrent.NewMap[common.LocalDate, []*common.Flight](),
}
}

Expand Down Expand Up @@ -82,7 +59,7 @@ func (fr *FlightRepo) Flights(ctx context.Context, start, end common.LocalDate)
}

func (fr *FlightRepo) flightsInternal(ctx context.Context, d common.LocalDate) ([]*common.Flight, error) {
if flights, ok := fr.cache.Get(d); ok {
if flights, ok := fr.cache.Load(d); ok {
return flights, nil
}

Expand All @@ -91,7 +68,7 @@ func (fr *FlightRepo) flightsInternal(ctx context.Context, d common.LocalDate) (
return nil, err
}

fr.cache.Set(d, flights)
fr.cache.Store(d, flights)
return flights, nil
}

Expand Down
Loading

0 comments on commit cdc2777

Please sign in to comment.