diff --git a/projects/ngrx.io/content/marketing/events.html b/projects/ngrx.io/content/marketing/events.html index 5f1756cec3..436c358dc7 100755 --- a/projects/ngrx.io/content/marketing/events.html +++ b/projects/ngrx.io/content/marketing/events.html @@ -2,73 +2,5 @@

Events

-

Upcoming Events presenting about NgRx:

- - - - - - - - - - - - - - - - - - - - - - - - - -
EventLocationDate
ngAtlantaAtlanta, GeorgiaJanuary 9 - 12, 2019
ng-IndiaGurgaon, IndiaFebruary 23, 2019
ng-confSalt Lake City, UtahMay 1 - 3, 2019
-

Past Events:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
EventLocationDate
NgColombiaMedellín, ColombiaSeptember 6 - 7th, 2018
DevFestATLAtlanta, GASeptember 22nd, 2018
Framework SummitPark City, UTSeptember 22nd, 2018
DevFestATLAtlanta, GAOctober 2 - 3rd, 2018
AngularMix - Use the discount code "RYAN" or "ROBERTS" to receive $50 off your registration!Orlando, FLOctober 10 - 12th, 2018
AngularConnectExcel, LondonNovember 6 - 7th, 2018
+
diff --git a/projects/ngrx.io/content/marketing/events.json b/projects/ngrx.io/content/marketing/events.json new file mode 100644 index 0000000000..74e3b6c033 --- /dev/null +++ b/projects/ngrx.io/content/marketing/events.json @@ -0,0 +1,115 @@ +[ + { + "name": "NgColombia", + "url": "http://www.ngcolombia.com", + "location": "Medellín, Colombia", + "startDate": "2018-09-06", + "endDate": "2018-09-07" + }, + { + "name": "DevFestATL", + "url": "http://devfestatl.com", + "location": "Atlanta, Georgia", + "endDate": "2018-09-22" + }, + { + "name": "Framework Summit", + "url": "http://frameworksummit.com", + "location": "Park City, Utah", + "endDate": "2018-09-22" + }, + { + "name": "DevFestATL", + "url": "http://devfestatl.com", + "location": "Atlanta, Georgia", + "startDate": "2018-10-02", + "endDate": "2018-10-03" + }, + { + "name": "AngularMix", + "url": "http://www.angularmix.com", + "location": "Orlando, Florida", + "startDate": "2018-10-10", + "endDate": "2018-10-12" + }, + { + "name": "AngularConnect", + "url": "https://www.angularconnect.com", + "location": "Excel, London", + "startDate": "2018-11-06", + "endDate": "2018-11-07" + }, + { + "name": "ngAtlanta", + "url": "http://ng-atl.org/", + "location": "Atlanta, Georgia", + "startDate": "2019-01-09", + "endDate": "2019-01-12" + }, + { + "name": "ng-India", + "url": "https://www.ng-ind.com", + "location": "Gurgaon, India", + "endDate": "2019-02-23" + }, + { + "name": "Open Source 101", + "url": "https://opensource101.com/", + "location": "Columbia, South Carolina", + "endDate": "2019-04-18" + }, + { + "name": "NG-Conf", + "url": "https://www.ng-conf.org", + "location": "Salt Lake City, Utah", + "startDate": "2019-05-01", + "endDate": "2019-05-03" + }, + { + "name": "ngVikings", + "url": "https://ngvikings.org/", + "location": "Copenhagen, Denmark", + "startDate": "2019-05-26", + "endDate": "2019-05-28" + }, + { + "name": "REFACTR.TECH", + "url": "https://refactr.tech/", + "location": "Atlanta, Georgia", + "startDate": "2019-06-05", + "endDate": "2019-06-07" + }, + { + "name": "AngularUP", + "url": "https://angular-up.com/", + "location": "Tel Aviv, Israel", + "endDate": "2019-06-12" + }, + { + "name": "NG-MY", + "url": "https://ng-my.org/", + "location": "Kuala Lumpur, Malaysia", + "startDate": "2019-07-06", + "endDate": "2019-07-07" + }, + { + "name": "ngDenver", + "url": "http://angulardenver.com/", + "location": "Denver, Colorado", + "startDate": "2019-08-01", + "endDate": "2019-08-02" + }, + { + "name": "AngularConnect", + "url": "https://www.angularconnect.com/", + "location": "London, United Kingdom", + "startDate": "2019-09-19", + "endDate": "2019-09-20" + }, + { + "name": "NG-Rome", + "url": "https://ngrome.io/", + "location": "Rome, Italy", + "endDate": "2019-10-07" + } +] diff --git a/projects/ngrx.io/src/app/custom-elements/element-registry.ts b/projects/ngrx.io/src/app/custom-elements/element-registry.ts index d83e70a726..c969c3f1c9 100644 --- a/projects/ngrx.io/src/app/custom-elements/element-registry.ts +++ b/projects/ngrx.io/src/app/custom-elements/element-registry.ts @@ -17,6 +17,10 @@ export const ELEMENT_MODULE_PATHS_AS_ROUTES = [ selector: 'aio-contributor-list', loadChildren: './contributor/contributor-list.module#ContributorListModule', }, + { + selector: 'aio-event-list', + loadChildren: './events/event-list.module#EventListModule', + }, { selector: 'aio-file-not-found-search', loadChildren: diff --git a/projects/ngrx.io/src/app/custom-elements/events/event-date-range.pipe.spec.ts b/projects/ngrx.io/src/app/custom-elements/events/event-date-range.pipe.spec.ts new file mode 100644 index 0000000000..5a9f284c53 --- /dev/null +++ b/projects/ngrx.io/src/app/custom-elements/events/event-date-range.pipe.spec.ts @@ -0,0 +1,64 @@ +import { EventDateRangePipe } from './event-date-range.pipe'; +import { Event } from './event.model'; + +describe('Pipe: Event Date Range', () => { + let pipe: EventDateRangePipe; + + beforeEach(() => { + pipe = new EventDateRangePipe(); + }); + + it('providing no startDate should format only the endDate in MM DD, YYYY', () => { + const event: Event = { + name: '', + url: '', + location: '', + endDate: new Date('2019-01-01') + }; + expect(pipe.transform(event)).toBe('January 1, 2019'); + }); + + it('providing the same startDate and endDate should format only one in MM DD, YYYY', () => { + const event: Event = { + name: '', + url: '', + location: '', + startDate: new Date('2019-01-01'), + endDate: new Date('2019-01-01') + }; + expect(pipe.transform(event)).toBe('January 1, 2019'); + }); + + it('providing different days in the same month and year should format in MM DD - DD, YYYY format', () => { + const event: Event = { + name: '', + url: '', + location: '', + startDate: new Date('2019-01-01'), + endDate: new Date('2019-01-02') + }; + expect(pipe.transform(event)).toBe('January 1 - 2, 2019'); + }); + + it('providing different days and months in the same year should format in MM DD - MM DD, YYYY format', () => { + const event: Event = { + name: '', + url: '', + location: '', + startDate: new Date('2019-01-01'), + endDate: new Date('2019-01-02') + }; + expect(pipe.transform(event)).toBe('January 1 - 2, 2019'); + }); + + it('providing different days, months, and years should format in MM DD, YYYY - MM DD, YYYY format', () => { + const event: Event = { + name: '', + url: '', + location: '', + startDate: new Date('2018-12-31'), + endDate: new Date('2019-01-01') + }; + expect(pipe.transform(event)).toBe('December 31, 2018 - January 1, 2019'); + }); +}); diff --git a/projects/ngrx.io/src/app/custom-elements/events/event-date-range.pipe.ts b/projects/ngrx.io/src/app/custom-elements/events/event-date-range.pipe.ts new file mode 100644 index 0000000000..f3d7076712 --- /dev/null +++ b/projects/ngrx.io/src/app/custom-elements/events/event-date-range.pipe.ts @@ -0,0 +1,55 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { Event } from './event.model'; + +/** + * Transforms the startDate and endDate for a given event into a dange range string. + * undefined until '01-01-2019' -> 'January 1, 2019' + * '01-01-2019' until '01-01-2019' -> 'January 1, 2019' + * '01-01-2019' until '01-02-2019' -> 'January 1 - 2, 2019' + * '01-28-2019' until '02-01-2019' -> 'January 28 - February 1, 2019' + * '12-31-2018' until '01-01-2019' -> 'December 31, 2018 - January 1, 2019' + */ +@Pipe({name: 'eventDateRange'}) +export class EventDateRangePipe implements PipeTransform { + transform(event: Event): string { + const startDate = event.startDate; + const endDate = event.endDate; + if (!startDate || startDate.getTime() === endDate.getTime()) { + return getDateString(endDate); + } else { + if (getMonth(startDate) === getMonth(endDate) && getYear(startDate) === getYear(endDate)) { + return getMonth(startDate) + + ' ' + getDay(startDate) + ' - ' + getDay(endDate) + + ', ' + getYear(startDate); + } else if (getYear(startDate) === getYear(endDate)) { + return getMonth(startDate) + + ' ' + getDay(startDate) + + ' - ' + getMonth(endDate) + + ' ' + getDay(endDate) + + ', ' + getYear(startDate); + } else { + return getDateString(startDate) + + ' - ' + getDateString(endDate); + } + } + } +} + +const getDay = (date: Date) => date.getUTCDate(); +const getMonth = (date: Date) => months[date.getUTCMonth()]; +const getYear = (date: Date) => date.getUTCFullYear(); +const getDateString = (date: Date) => getMonth(date) + ' ' + getDay(date) + ', ' + getYear(date); +const months = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' +]; diff --git a/projects/ngrx.io/src/app/custom-elements/events/event-list.component.spec.ts b/projects/ngrx.io/src/app/custom-elements/events/event-list.component.spec.ts new file mode 100644 index 0000000000..3c6a39c13a --- /dev/null +++ b/projects/ngrx.io/src/app/custom-elements/events/event-list.component.spec.ts @@ -0,0 +1,57 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { EventListComponent } from './event-list.component'; +import { of } from 'rxjs'; +import { EventService } from './event.service'; +import { Event } from './event.model'; +import { EventDateRangePipe } from './event-date-range.pipe'; + +const mockUpcomingEvents: Event[] = [ + { + name: 'NG-Rome', + url: 'https://ngrome.io/', + location: 'Rome, Italy', + endDate: new Date('2019-10-07') + } +]; + +const mockPastEvents: Event[] = [ + { + name: 'DevFestATL', + url: 'http://devfestatl.com', + location: 'Atlanta, Georgia', + endDate: new Date('2018-09-22') + } +]; + +class TestEventService { + upcomingEvents$ = of(mockUpcomingEvents); + pastEvents$ = of(mockPastEvents); +} + +describe('Event List Component', () => { + + let fixture: ComponentFixture; + let component: EventListComponent; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [ EventListComponent, EventDateRangePipe ], + providers: [{ provide: EventService, useClass: TestEventService }] + }); + + fixture = TestBed.createComponent(EventListComponent); + component = fixture.componentInstance; + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should set the upcomingEvents', () => { + component.upcomingEvents$.subscribe(events => expect(events).toEqual(mockUpcomingEvents)); + }); + + it('should set the pastEvents', () => { + component.pastEvents$.subscribe(events => expect(events).toEqual(mockPastEvents)); + }); +}); diff --git a/projects/ngrx.io/src/app/custom-elements/events/event-list.component.ts b/projects/ngrx.io/src/app/custom-elements/events/event-list.component.ts new file mode 100644 index 0000000000..08e5fa117c --- /dev/null +++ b/projects/ngrx.io/src/app/custom-elements/events/event-list.component.ts @@ -0,0 +1,50 @@ +import { Component } from '@angular/core'; +import { Event } from './event.model'; +import { EventService } from './event.service'; +import { Observable } from 'rxjs'; + +@Component({ + selector: `aio-event-list`, + template: ` +

Upcoming Events presenting about NgRx:

+ + + + + + + + + + + + + + + +
EventLocationDate
{{upcomingEvent.name}}{{upcomingEvent.location}}{{upcomingEvent | eventDateRange}}
+

Past Events:

+ + + + + + + + + + + + + + + +
EventLocationDate
{{pastEvent.name}}{{pastEvent.location}}{{pastEvent | eventDateRange}}
+` +}) +export class EventListComponent { + upcomingEvents$: Observable = this.eventService.upcomingEvents$; + pastEvents$: Observable = this.eventService.pastEvents$; + + constructor(private eventService: EventService) { } +} diff --git a/projects/ngrx.io/src/app/custom-elements/events/event-list.module.ts b/projects/ngrx.io/src/app/custom-elements/events/event-list.module.ts new file mode 100644 index 0000000000..0a5e8cb060 --- /dev/null +++ b/projects/ngrx.io/src/app/custom-elements/events/event-list.module.ts @@ -0,0 +1,16 @@ +import { NgModule, Type } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { WithCustomElementComponent } from '../element-registry'; +import { EventListComponent } from './event-list.component'; +import { EventService } from './event.service'; +import { EventDateRangePipe } from './event-date-range.pipe'; + +@NgModule({ + imports: [ CommonModule ], + declarations: [ EventListComponent, EventDateRangePipe ], + entryComponents: [ EventListComponent ], + providers: [ EventService ] +}) +export class EventListModule implements WithCustomElementComponent { + customElementComponent: Type = EventListComponent; +} diff --git a/projects/ngrx.io/src/app/custom-elements/events/event.model.ts b/projects/ngrx.io/src/app/custom-elements/events/event.model.ts new file mode 100644 index 0000000000..6a4599f67f --- /dev/null +++ b/projects/ngrx.io/src/app/custom-elements/events/event.model.ts @@ -0,0 +1,15 @@ +export interface EventResponse { + name: string; + url: string; + location: string; + startDate?: string; + endDate: string; +} + +export interface Event { + name: string; + url: string; + location: string; + startDate?: Date; + endDate: Date; +} diff --git a/projects/ngrx.io/src/app/custom-elements/events/event.service.spec.ts b/projects/ngrx.io/src/app/custom-elements/events/event.service.spec.ts new file mode 100644 index 0000000000..ed51276211 --- /dev/null +++ b/projects/ngrx.io/src/app/custom-elements/events/event.service.spec.ts @@ -0,0 +1,116 @@ +import { + HttpClientTestingModule, + HttpTestingController, +} from '@angular/common/http/testing'; +import { Injector } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { EventService } from './event.service'; +import { Event, EventResponse } from './event.model'; + +describe('Event Service', () => { + let injector: Injector; + let eventService: EventService; + let httpMock: HttpTestingController; + + beforeEach(() => { + injector = TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [EventService], + }); + + eventService = injector.get(EventService); + eventService.currentDate = new Date('2019-01-02'); + httpMock = injector.get(HttpTestingController); + }); + + afterEach(() => httpMock.verify()); + + it('should make a single connection to the server', () => { + const req = httpMock.expectOne({}); + expect(req.request.url).toBe('generated/events.json'); + }); + + describe('events', () => { + let upcomingEvents: Event[]; + let pastEvents: Event[]; + let testData: EventResponse[]; + + beforeEach(() => { + testData = getTestEventResponse(); + httpMock.expectOne({}).flush(testData); + eventService.upcomingEvents$.subscribe(results => (upcomingEvents = results)); + eventService.pastEvents$.subscribe(results => (pastEvents = results)); + }); + + it('upcomingEvents$ observable should complete', () => { + let completed = false; + eventService.upcomingEvents$.subscribe( + undefined, + undefined, + () => (completed = true) + ); + expect(completed).toBe(true, 'observable completed'); + }); + + it('pastEvents$ observable should complete', () => { + let completed = false; + eventService.pastEvents$.subscribe( + undefined, + undefined, + () => (completed = true) + ); + expect(completed).toBe(true, 'observable completed'); + }); + + describe('upcoming events', () => { + it('should emit', () => { + expect(upcomingEvents.length).toEqual(3) + }); + }); + + describe('past events', () => { + it('should emit', () => { + expect(pastEvents.length).toEqual(2) + }); + }); + }); +}); + +function getTestEventResponse(): EventResponse[] { + return [ + { + name: 'conf1', + url: '', + location: '', + startDate: '2018-06-28', + endDate: '2018-07-01' + }, + { + name: 'conf2', + url: '', + location: '', + startDate: '2018-12-25', + endDate: '2019-01-01' + }, + { + name: 'conf3', + url: '', + location: '', + startDate: '2019-01-01', + endDate: '2019-01-02' + }, + { + name: 'conf4', + url: '', + location: '', + endDate: '2019-04-01' + }, + { + name: 'conf5', + url: '', + location: '', + startDate: '2019-04-02', + endDate: '2019-04-02' + }, + ]; +} diff --git a/projects/ngrx.io/src/app/custom-elements/events/event.service.ts b/projects/ngrx.io/src/app/custom-elements/events/event.service.ts new file mode 100644 index 0000000000..dccf0394b9 --- /dev/null +++ b/projects/ngrx.io/src/app/custom-elements/events/event.service.ts @@ -0,0 +1,55 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { ConnectableObservable, Observable } from 'rxjs'; +import { publishLast, map } from 'rxjs/operators'; +import { CONTENT_URL_PREFIX } from 'app/documents/document.service'; +import { EventResponse, Event } from './event.model'; + +const eventsPath = CONTENT_URL_PREFIX + 'events.json'; + +@Injectable() +export class EventService { + currentDate: Date; + private events$: Observable; + upcomingEvents$: Observable; + pastEvents$: Observable; + + constructor(private http: HttpClient) { + const now = new Date(); + // Compare soley on UTC date, without factoring in time. + this.currentDate = new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate())); + this.events$ = this.getEvents(); + this.upcomingEvents$ = this.events$.pipe( + map(events => + events.filter(event => event.endDate >= this.currentDate) + ) + ); + this.pastEvents$ = this.events$.pipe( + map(events => + events.filter(event => event.endDate < this.currentDate) + ) + ); + } + + /** + * Fetch Event JSON from file and return an Observable that emits an Event array. + */ + private getEvents(): Observable { + const events = this.http.get(eventsPath).pipe( + map(eventResponses => + eventResponses.map(eventResponse => { + const event: Event = { + ...eventResponse, + startDate: eventResponse.startDate ? new Date(eventResponse.startDate) : undefined, + endDate: new Date(eventResponse.endDate) + }; + return event; + }) + ), + publishLast(), + ); + + (events as ConnectableObservable).connect(); + return events; + }; +} diff --git a/projects/ngrx.io/tools/transforms/angular-content-package/index.js b/projects/ngrx.io/tools/transforms/angular-content-package/index.js index b0635ae198..973e2d7b8f 100644 --- a/projects/ngrx.io/tools/transforms/angular-content-package/index.js +++ b/projects/ngrx.io/tools/transforms/angular-content-package/index.js @@ -82,6 +82,11 @@ module.exports = new Package('angular-content', [basePackage, contentPackage]) include: CONTENTS_PATH + '/marketing/resources.json', fileReader: 'jsonFileReader' }, + { + basePath: CONTENTS_PATH, + include: CONTENTS_PATH + '/marketing/events.json', + fileReader: 'jsonFileReader' + }, ]); collectExamples.exampleFolders.push('examples'); @@ -110,7 +115,8 @@ module.exports = new Package('angular-content', [basePackage, contentPackage]) {docTypes: ['navigation-json'], pathTemplate: '${id}', outputPathTemplate: '../${id}.json'}, {docTypes: ['contributors-json'], pathTemplate: '${id}', outputPathTemplate: '../${id}.json'}, {docTypes: ['announcements-json'], pathTemplate: '${id}', outputPathTemplate: '../${id}.json'}, - {docTypes: ['resources-json'], pathTemplate: '${id}', outputPathTemplate: '../${id}.json'} + {docTypes: ['resources-json'], pathTemplate: '${id}', outputPathTemplate: '../${id}.json'}, + {docTypes: ['events-json'], pathTemplate: '${id}', outputPathTemplate: '../${id}.json'} ]); })