Skip to content

Commit

Permalink
added poll org events (#325)
Browse files Browse the repository at this point in the history
  • Loading branch information
Klaven authored Apr 8, 2023
1 parent 68bb3cc commit 775936c
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 0 deletions.
41 changes: 41 additions & 0 deletions examples/poll_org_events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use octocrab::{etag::Etagged, models::events::Event, Page};
use std::collections::VecDeque;

const DELAY_MS: u64 = 500;
const TRACKING_CAPACITY: usize = 20;

#[tokio::main]
async fn main() -> octocrab::Result<()> {
let mut etag = None;
let mut seen = VecDeque::with_capacity(TRACKING_CAPACITY);
let octo = octocrab::instance();
loop {
let response: Etagged<Page<Event>> = octo
.orgs("nixos")
.events()
.etag(etag)
.per_page(10)
.send()
.await?;
if let Some(page) = response.value {
for event in page {
// If an etag changes and we get a new page, this page may contain events we have
// already seen along with new events. So, keep track of the ones we have seen for
// each page, this will be at most 20 events - the current page of 10 events and
// the last page.
if !seen.contains(&event.id) {
println!(
"New event : id = {:?}, repo = {:?}, type = {:?}, time = {:?}",
event.id, event.repo.name, event.r#type, event.created_at
);
if seen.len() == TRACKING_CAPACITY {
seen.pop_back();
}
seen.push_front(event.id);
}
}
}
etag = response.etag;
tokio::time::sleep(tokio::time::Duration::from_millis(DELAY_MS)).await;
}
}
33 changes: 33 additions & 0 deletions src/api/orgs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
mod list_members;
mod list_repos;
mod events;

use crate::error::HttpSnafu;
use crate::Octocrab;
Expand All @@ -10,6 +11,7 @@ use snafu::ResultExt;

pub use self::list_members::ListOrgMembersBuilder;
pub use self::list_repos::ListReposBuilder;
pub use self::events::ListOrgEventsBuilder;

/// A client to GitHub's organization API.
///
Expand Down Expand Up @@ -134,6 +136,37 @@ impl<'octo> OrgHandler<'octo> {
list_repos::ListReposBuilder::new(self)
}

/// List events on this organization.
///
/// Takes an optional etag which allows for efficient polling. Here is a quick example to poll a
/// organization's events.
/// ```no_run
/// # use std::convert::TryFrom;
/// # use octocrab::{models::events::Event, etag::{Etagged,EntityTag}, Page};
/// # async fn run() -> octocrab::Result<()> {
/// let mut etag = None;
/// loop {
/// let response: Etagged<Page<Event>> = octocrab::instance()
/// .orgs("owner")
/// .events()
/// .etag(etag)
/// .send()
/// .await?;
/// if let Some(page) = response.value {
/// // do something with the page ...
/// } else {
/// println!("No new data received, trying again soon");
/// }
/// etag = response.etag;
/// // add a delay before the next iteration
/// }
/// # Ok(())
/// # }
/// ```
pub fn events(&self) -> events::ListOrgEventsBuilder<'_, '_> {
events::ListOrgEventsBuilder::new(self)
}

/// Creates a new webhook for the specified organization.
///
/// # Notes
Expand Down
95 changes: 95 additions & 0 deletions src/api/orgs/events.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//! GitHub Organization Events
use crate::{
etag::{EntityTag, Etagged},
models::events,
orgs::OrgHandler,
FromResponse, Page,
};
use http::request::Builder;
use http::{header::HeaderMap, Method, StatusCode};

pub struct ListOrgEventsBuilder<'octo, 'handler> {
handler: &'handler OrgHandler<'octo>,
headers: Headers,
params: Params,
}

struct Headers {
etag: Option<EntityTag>,
}

#[derive(serde::Serialize)]
struct Params {
#[serde(skip_serializing_if = "Option::is_none")]
per_page: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
page: Option<u32>,
}

impl<'octo, 'handler> ListOrgEventsBuilder<'octo, 'handler> {
pub(crate) fn new(handler: &'handler OrgHandler<'octo>) -> Self {
Self {
handler,
headers: Headers { etag: None },
params: Params {
per_page: None,
page: None,
},
}
}

/// Etag for this request.
pub fn etag(mut self, etag: Option<EntityTag>) -> Self {
self.headers.etag = etag;
self
}

/// Results per page (max 100).
pub fn per_page(mut self, per_page: impl Into<u8>) -> Self {
self.params.per_page = Some(per_page.into());
self
}

/// Page number of the results to fetch.
pub fn page(mut self, page: impl Into<u32>) -> Self {
self.params.page = Some(page.into());
self
}

/// Sends the actual request.
pub async fn send(self) -> crate::Result<Etagged<Page<events::Event>>> {
let route = format!(
"/orgs/{owner}/events",
owner = self.handler.owner
);

let uri = self
.handler
.crab
.parameterized_uri(route, Some(&self.params))?;

let mut headers = HeaderMap::new();
if let Some(etag) = self.headers.etag {
EntityTag::insert_if_none_match_header(&mut headers, etag)?;
}

let mut request = Builder::new().uri(uri).method(Method::GET);
for (key, value) in headers.iter() {
request = request.header(key, value);
}

let request = self.handler.crab.build_request(request, None::<&()>)?;
let response = self.handler.crab.execute(request).await?;
let etag = EntityTag::extract_from_response(&response);
if response.status() == StatusCode::NOT_MODIFIED {
Ok(Etagged { etag, value: None })
} else {
<Page<events::Event>>::from_response(crate::map_github_error(response).await?)
.await
.map(|page| Etagged {
etag,
value: Some(page),
})
}
}
}

0 comments on commit 775936c

Please sign in to comment.