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

Guide: A Hierarchy of Context #83

Open
wants to merge 5 commits into
base: source
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions resources/templates/md/posts/2020-06-11-a-hierarchy-of-context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
{:title "A Hierarchy of Context"
:layout :post
:toc true
:author "@devth"
:summary "Mine, ours, theirs"
:tags ["2020" "ops" "config"]}

```yetibot
!complete context is
```

Context is everything. It acts as a constraining factor, letting us make
assumptions, frames ideas, and is at the heart of every delightful UX and DX.

In chat systems, we can construct a hierarchy of context. From widest to
narrowest:

- **Global**: when a message arrives, we ignore everything about its origin,
such as the adapter and channel it originated from or the user responsible for
sending it. The global context spans an entire Yetibot instance across all
configured adapters.

- **Adapter**: slightly more specific than global, when a message arrives we
dispatch on the adapter that it originated from.

- **Channel**: next on the spectrum, we dispatch on the channel that a message
originated from, but again ignore who sent it. The channel context is useful
for parameterizing things at the **team** level, such as the issue tracker
project key, ssh servers, github repos, the group's favorite stock prices,
whether or not embedded commands are enabled, or the local fallback command.

- **Our**: this context is similar to **channel**, except it's dynamically
determined by the set of users present in a channel. That means when a user
joins or leaves a channel, the context of values are automatically adjusted,
making it a more organic set of values than channel KVs.

- **User**: finally, we dispatch on user who sent a message. This is our
narrowest context, and is the lever for varying parameters between users. This
may be a user's zip code, GitHub username, or any other user-specific
parameter that could vary across built in commands or aliases.

## Usage and examples

So how do we use this?

We can construct aliases that rely on any of these available contexts. But we
can also rely on cascading fallbacks that start with the most specific and
progressively fall back to the next-most specific to obtain the value of a
parameter.

Let's use some examples to illustrate.

### Weather

**Use case**: quickly list the current weather for all members in the channel.

There are a couple ways to do this. But first, let's define a helper alias:

```yetibot
!alias loctemp = "weather $s | render {{temp|multiply:1.8|add:32|round}}°F, Humidity {{rh}}% - {{weather.description}}, {{city_name}}, {{state_code}} {{country_code}}"
```

Try it:

```yetibot
!loctemp Seattle
```

#### Channel settings

Now, let's say you have people in the channel from Seattle, Chicago, and New
York City. We can encode that in a channel setting:

```yetibot
!channel set locs = Seattle, Chicago, New York City
```

```yetibot
!alias chantemps = "channel settings locs | list | xargs loctemp | sort"
```

Finally, try it out:

```yetibot
!chantemps
```

This allows us to vary the set of `locs` from channel to channel.
The result of `chantemps` will reflect the local channel settings.

#### User settings

Another way to solve this weather look up use case would be for individuals to
set their individual locations via `my`.

```yetibot
!help my
```

This is interesting, because once a user sets their location, a command that
utilizes the locations of all members in a channel would vary from channel to
channel automatically, depending on who is present. When a user leaves or joins
a channel, the context is updated accordingly.

We can get that list of values using `our`:

```yetibot
!help our
```

```yetibot
!our zip
```

### Stocks

Use case: list stock prices.

This time let's rely on `ours`.

```yetibot
!my stocks = aapl,goog,tsla
```

Now if another user specified:

```yetibot
```

### Hierarchy

The above examples demonstrate how to pull values from a specific context, but
we can also dynamically resolve a value from the narrowest available context.


### Dealing with unset values

So far we've assumed config

## Future

In the future, as part of
[The great configuration overhaul of 2020](https://github.com/yetibot/yetibot/projects/5),
we may provide the ability to override immutable Yetibot configuration at
runtime with mutable (i.e. stored in the database) config.

2 changes: 1 addition & 1 deletion resources/templates/themes/devth/js/dist.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ if (team) {
}), team);
}

const endpoint = "https://public.yetibot.com/graphql";
// const endpoint = "https://public.yetibot.com/graphql";
// switch to this for local dev against yetibot.core
// const endpoint = "http://localhost:3003/graphql";
const endpoint = "http://localhost:3003/graphql";
const client = new ApolloClient({uri: endpoint});

// TOC interactive - desktop only for now
Expand Down