,
40 | props: OP,
41 | state: void
42 | }
43 |
44 | declare type ConnectedComponentClass
50 | ) => ConnectedComponentClass
136 | Learn more about a specific version.
137 |
138 | Select a version number from the left menu.
139 |
140 |
331 | Learn more about a specific version.
332 | Select a version number from the left menu.
333 | extends React$Component<{
53 | store: Store,
54 | children?: any
55 | }> {}
56 |
57 | declare function createProvider(
58 | storeKey?: string,
59 | subKey?: string
60 | ): Provider<*, *>;
61 |
62 | declare type ConnectOptions = {
63 | pure?: boolean,
64 | withRef?: boolean
65 | };
66 |
67 | declare type Null = null | void;
68 |
69 | declare function connect(
70 | ...rest: Array(
81 | mapStateToProps: MapStateToProps,
82 | mapDispatchToProps: Null,
83 | mergeProps: Null,
84 | options?: ConnectOptions
85 | ): Connector(
95 | mapStateToProps: MapStateToProps,
96 | mapDispatchToProps: MapDispatchToProps,
97 | mergeProps: Null,
98 | options?: ConnectOptions
99 | ): Connector(
102 | mapStateToProps: MapStateToProps,
103 | mapDispatchToProps: Null,
104 | mergeProps: MergeProps(
109 | mapStateToProps: MapStateToProps,
110 | mapDispatchToProps: MapDispatchToProps,
111 | mergeProps: MergeProps
7 |
10 | Delivery Dashboard
11 |
12 |
13 |
35 | Firefox Releases
36 |
37 |
38 |
129 | = () => S;
15 | declare type ThunkAction = (
16 | dispatch: Dispatch,
17 | GetState,
18 | ) => R;
19 | declare type ThunkDispatch = ) => R;
20 | declare type DispatchAPI = (action: A) => A;
21 | declare type PlainDispatch = PlainDispatch & ThunkDispatch;
23 |
24 | declare type MiddlewareAPI> = {
25 | dispatch: D,
26 | getState(): S,
27 | };
28 |
29 | declare type Store> = {
30 | // rewrite MiddlewareAPI members in order to get nicer error messages (intersections produce long messages)
31 | dispatch: D,
32 | getState(): S,
33 | subscribe(listener: () => void): () => void,
34 | replaceReducer(nextReducer: Reducer): void,
35 | };
36 |
37 | declare type Reducer = (state: S, action: A) => S;
38 |
39 | declare type CombinedReducer = (
40 | state: ($Shape & {}) | void,
41 | action: A,
42 | ) => S;
43 |
44 | declare type Middleware> = (
45 | api: MiddlewareAPI,
46 | ) => (next: D) => D;
47 |
48 | declare type StoreCreator> = {
49 | (reducer: Reducer, enhancer?: StoreEnhancer): Store,
50 | (
51 | reducer: Reducer,
52 | preloadedState: S,
53 | enhancer?: StoreEnhancer,
54 | ): Store,
55 | };
56 |
57 | declare type StoreEnhancer> = (
58 | next: StoreCreator,
59 | ) => StoreCreator;
60 |
61 | declare function createStore(
62 | reducer: Reducer,
63 | enhancer?: StoreEnhancer,
64 | ): Store;
65 | declare function createStore(
66 | reducer: Reducer,
67 | preloadedState: S,
68 | enhancer?: StoreEnhancer,
69 | ): Store;
70 |
71 | declare function applyMiddleware(
72 | ...middlewares: Array;
74 |
75 | declare type ActionCreator = (...args: Array) => A;
76 | declare type ActionCreators) => S>, A>;
101 |
102 | declare function compose(ab: (a: A) => B): (a: A) => B;
103 | declare function compose(
104 | bc: (b: B) => C,
105 | ab: (a: A) => B,
106 | ): (a: A) => C;
107 | declare function compose(
108 | cd: (c: C) => D,
109 | bc: (b: B) => C,
110 | ab: (a: A) => B,
111 | ): (a: A) => D;
112 | declare function compose(
113 | de: (d: D) => E,
114 | cd: (c: C) => D,
115 | bc: (b: B) => C,
116 | ab: (a: A) => B,
117 | ): (a: A) => E;
118 | declare function compose(
119 | ef: (e: E) => F,
120 | de: (d: D) => E,
121 | cd: (c: C) => D,
122 | bc: (b: B) => C,
123 | ab: (a: A) => B,
124 | ): (a: A) => F;
125 | declare function compose(
126 | fg: (f: F) => G,
127 | ef: (e: E) => F,
128 | de: (d: D) => E,
129 | cd: (c: C) => D,
130 | bc: (b: B) => C,
131 | ab: (a: A) => B,
132 | ): (a: A) => G;
133 | declare function compose(
134 | gh: (g: G) => H,
135 | fg: (f: F) => G,
136 | ef: (e: E) => F,
137 | de: (d: D) => E,
138 | cd: (c: C) => D,
139 | bc: (b: B) => C,
140 | ab: (a: A) => B,
141 | ): (a: A) => H;
142 | declare function compose(
143 | hi: (h: H) => I,
144 | gh: (g: G) => H,
145 | fg: (f: F) => G,
146 | ef: (e: E) => F,
147 | de: (d: D) => E,
148 | cd: (c: C) => D,
149 | bc: (b: B) => C,
150 | ab: (a: A) => B,
151 | ): (a: A) => I;
152 | }
153 |
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | // In production, we register a service worker to serve assets from local cache.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on the "N+1" visit to a page, since previously
7 | // cached resources are updated in the background.
8 |
9 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
10 | // This link also includes instructions on opting out of this behavior.
11 |
12 | const isLocalhost: boolean = Boolean(
13 | window.location.hostname === 'localhost' ||
14 | // [::1] is the IPv6 localhost address.
15 | window.location.hostname === '[::1]' ||
16 | // 127.0.0.1/8 is considered localhost for IPv4.
17 | window.location.hostname.match(
18 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/,
19 | ),
20 | );
21 |
22 | export default function register() {
23 | if (
24 | process.env.NODE_ENV === 'production' &&
25 | 'serviceWorker' in navigator &&
26 | process.env.PUBLIC_URL &&
27 | navigator.serviceWorker
28 | ) {
29 | // The URL constructor is available in all browsers that support SW.
30 | const publicUrl = new URL(
31 | process.env.PUBLIC_URL,
32 | window.location.toString(),
33 | );
34 | if (publicUrl.origin !== window.location.origin) {
35 | // Our service worker won't work if PUBLIC_URL is on a different origin
36 | // from what our page is served on. This might happen if a CDN is used to
37 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
38 | return;
39 | }
40 |
41 | window.addEventListener('load', () => {
42 | if (process.env.PUBLIC_URL) {
43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
44 |
45 | if (!isLocalhost) {
46 | // Is not local host. Just register service worker
47 | registerValidSW(swUrl);
48 | } else {
49 | // This is running on localhost. Lets check if a service worker still exists or not.
50 | checkValidServiceWorker(swUrl);
51 | }
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl) {
58 | navigator.serviceWorker &&
59 | navigator.serviceWorker
60 | .register(swUrl)
61 | .then(registration => {
62 | registration.onupdatefound = () => {
63 | const installingWorker = registration.installing;
64 | installingWorker.onstatechange = () => {
65 | if (installingWorker.state === 'installed') {
66 | if (
67 | navigator.serviceWorker &&
68 | navigator.serviceWorker.controller
69 | ) {
70 | // At this point, the old content will have been purged and
71 | // the fresh content will have been added to the cache.
72 | // It's the perfect time to display a "New content is
73 | // available; please refresh." message in your web app.
74 | console.log('New content is available; please refresh.');
75 | } else {
76 | // At this point, everything has been precached.
77 | // It's the perfect time to display a
78 | // "Content is cached for offline use." message.
79 | console.log('Content is cached for offline use.');
80 | }
81 | }
82 | };
83 | };
84 | })
85 | .catch(error => {
86 | console.error('Error during service worker registration:', error);
87 | });
88 | }
89 |
90 | function checkValidServiceWorker(swUrl) {
91 | // Check if the service worker can be found. If it can't reload the page.
92 | fetch(swUrl)
93 | .then(response => {
94 | // Ensure service worker exists, and that we really are getting a JS file.
95 | if (
96 | response.status === 404 ||
97 | response.headers.get('content-type').indexOf('javascript') === -1
98 | ) {
99 | // No service worker found. Probably a different app. Reload the page.
100 | navigator.serviceWorker &&
101 | navigator.serviceWorker.ready.then(registration => {
102 | registration.unregister().then(() => {
103 | window.location.reload();
104 | });
105 | });
106 | } else {
107 | // Service worker found. Proceed as normal.
108 | registerValidSW(swUrl);
109 | }
110 | })
111 | .catch(() => {
112 | console.log(
113 | 'No internet connection found. App is running in offline mode.',
114 | );
115 | });
116 | }
117 |
118 | export function unregister() {
119 | if ('serviceWorker' in navigator && navigator.serviceWorker) {
120 | navigator.serviceWorker.ready.then(registration => {
121 | registration.unregister();
122 | });
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/actions.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import {
4 | ADD_CHECK_RESULT,
5 | REFRESH_CHECK_RESULT,
6 | ADD_SERVER_ERROR,
7 | SET_VERSION,
8 | UPDATE_PRODUCT_VERSIONS,
9 | UPDATE_POLLBOT_VERSION,
10 | UPDATE_RELEASE_INFO,
11 | REQUEST_ONGOING_VERSIONS,
12 | REQUEST_POLLBOT_VERSION,
13 | UPDATE_URL,
14 | REFRESH_STATUS,
15 | REQUEST_STATUS,
16 | REQUEST_LOGIN,
17 | REQUEST_LOGOUT,
18 | LOGGED_IN,
19 | LOGGED_OUT,
20 | LOGIN_REQUESTED,
21 | UPDATE_USER_INFO,
22 | } from './types';
23 | import type {
24 | AddCheckResult,
25 | AddServerError,
26 | APIVersionData,
27 | CheckResult,
28 | LoginRequested,
29 | LoggedIn,
30 | LoggedOut,
31 | VersionsDict,
32 | Product,
33 | RefreshCheckResult,
34 | RefreshStatus,
35 | ReleaseInfo,
36 | RequestLogin,
37 | RequestLogout,
38 | RequestOngoingVersions,
39 | RequestPollbotVersion,
40 | RequestStatus,
41 | SetVersion,
42 | UpdateProductVersions,
43 | UpdatePollbotVersion,
44 | UpdateReleaseInfo,
45 | UpdateUrl,
46 | UpdateUserInfo,
47 | } from './types';
48 |
49 | // Small utility function.
50 | export const localUrlFromVersion = ([product, version]: [Product, string]) =>
51 | `#pollbot/${product}/${version}`;
52 |
53 | /*
54 | * action creators
55 | */
56 |
57 | export function setVersion(product: Product, version: string): SetVersion {
58 | return {type: SET_VERSION, product, version};
59 | }
60 |
61 | export const sortByVersion = (a: string, b: string) => {
62 | const partsA = a.split('.');
63 | const partsB = b.split('.');
64 | if (partsA.length < 2 || partsB.length < 2) {
65 | // Bogus version, list it last.
66 | return 1;
67 | }
68 | let i = 0;
69 | while (partsA[i] === partsB[i] && i <= partsA.length) {
70 | // Skip all the parts that are equal.
71 | i++;
72 | }
73 | if (!partsA[i] || !partsB[i]) {
74 | // Both versions have the same first parts, but one may have more parts, eg
75 | // 56.0 and 56.0.1.
76 | return partsB.length - partsA.length;
77 | }
78 | // We have been through all the similar parts, we now have to deal with the
79 | // first part which is different.
80 | const subPartRegex = /^(\d+)([a-zA-Z]+)?(\d+)?([a-zA-Z]+)?/; // Eg: 0b12pre
81 | const subPartA = partsA[i].match(subPartRegex); // Eg: ["0b1pre", "0", "b", "12", "pre"]
82 | const subPartB = partsB[i].match(subPartRegex);
83 | if (!subPartA || !subPartB) {
84 | // Bogus version, list it last.
85 | return 1;
86 | }
87 | if (subPartA[1] !== subPartB[1]) {
88 | return parseInt(subPartB[1], 10) - parseInt(subPartA[1], 10);
89 | }
90 | if (subPartA[2] !== subPartB[2]) {
91 | // Suffix like 'a' or 'b'.
92 | if (subPartA[2] && !subPartB[2]) {
93 | return 1;
94 | }
95 | if (subPartB[2] && !subPartA[2]) {
96 | return -1;
97 | }
98 | return subPartB[2].localeCompare(subPartA[2]);
99 | }
100 | return parseInt(subPartB[3], 10) - parseInt(subPartA[3], 10);
101 | };
102 |
103 | export const capitalize = (item: string) =>
104 | item.charAt(0).toUpperCase() + item.slice(1);
105 |
106 | export const capitalizeChannel = ([channel, version]: [string, string]) => [
107 | capitalize(channel),
108 | version,
109 | ];
110 |
111 | export function updateProductVersions(
112 | product: Product,
113 | versions: VersionsDict,
114 | ): UpdateProductVersions {
115 | return {type: UPDATE_PRODUCT_VERSIONS, product, versions};
116 | }
117 |
118 | export function updatePollbotVersion(
119 | version: APIVersionData,
120 | ): UpdatePollbotVersion {
121 | return {type: UPDATE_POLLBOT_VERSION, version};
122 | }
123 |
124 | export function updateReleaseInfo(releaseInfo: ReleaseInfo): UpdateReleaseInfo {
125 | return {type: UPDATE_RELEASE_INFO, releaseInfo};
126 | }
127 |
128 | export function addCheckResult(
129 | title: string,
130 | result: CheckResult,
131 | ): AddCheckResult {
132 | return {type: ADD_CHECK_RESULT, title, result};
133 | }
134 |
135 | export function refreshCheckResult(title: string): RefreshCheckResult {
136 | return {type: REFRESH_CHECK_RESULT, title};
137 | }
138 |
139 | export function addServerError(title: string, err: string): AddServerError {
140 | return {type: ADD_SERVER_ERROR, title, err};
141 | }
142 |
143 | export function loggedIn(): LoggedIn {
144 | return {type: LOGGED_IN};
145 | }
146 |
147 | export function loggedOut(): LoggedOut {
148 | return {type: LOGGED_OUT};
149 | }
150 |
151 | export function loginRequested(): LoginRequested {
152 | return {type: LOGIN_REQUESTED};
153 | }
154 |
155 | // For sagas
156 | export function requestPollbotVersion(): RequestPollbotVersion {
157 | return {type: REQUEST_POLLBOT_VERSION};
158 | }
159 |
160 | export function requestOngoingVersions(): RequestOngoingVersions {
161 | return {type: REQUEST_ONGOING_VERSIONS};
162 | }
163 |
164 | export function updateUrl(): UpdateUrl {
165 | return {type: UPDATE_URL};
166 | }
167 |
168 | export function refreshStatus(): RefreshStatus {
169 | return {type: REFRESH_STATUS};
170 | }
171 |
172 | export function requestStatus(
173 | product: Product,
174 | version: string,
175 | ): RequestStatus {
176 | return {type: REQUEST_STATUS, product, version};
177 | }
178 |
179 | export function requestLogin(): RequestLogin {
180 | return {type: REQUEST_LOGIN};
181 | }
182 |
183 | export function requestLogout(): RequestLogout {
184 | return {type: REQUEST_LOGOUT};
185 | }
186 |
187 | export function updateUserInfo(userInfo: any): UpdateUserInfo {
188 | return {type: UPDATE_USER_INFO, userInfo};
189 | }
190 |
--------------------------------------------------------------------------------
/src/types.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import type {
3 | Store as ReduxStore,
4 | ThunkAction as ReduxThunkAction,
5 | Dispatch as ReduxDispatch,
6 | GetState as ReduxGetState,
7 | } from 'redux';
8 |
9 | export const products = ['firefox', 'devedition'];
10 |
11 | /*
12 | * state types
13 | */
14 | export type Product = 'firefox' | 'devedition';
15 | export type Status = 'missing' | 'exists' | 'incomplete' | 'error';
16 |
17 | export type ChannelVersion = [string, string];
18 | export type ChannelVersions = ChannelVersion[];
19 | export type VersionsDict = {[channel: string]: string};
20 | export type ProductVersions = {
21 | [product: Product]: VersionsDict,
22 | };
23 |
24 | export type CheckInfo = {
25 | +url: string,
26 | +title: string,
27 | +actionable: boolean,
28 | };
29 |
30 | export type ReleaseInfo = {
31 | +channel: string,
32 | +product: Product,
33 | +version: string,
34 | +checks: CheckInfo[],
35 | +message: string,
36 | +status: number,
37 | };
38 |
39 | export type CheckResult = {
40 | +status: Status,
41 | +message: string,
42 | +link: string,
43 | };
44 |
45 | export type CheckResults = {
46 | [check: string]: CheckResult,
47 | };
48 |
49 | export type APIVersionData = {
50 | name: string,
51 | version: string,
52 | source: string,
53 | commit: string,
54 | };
55 |
56 | /* Error: [title, errorMessage] */
57 | export type Error = [string, string];
58 |
59 | export type Login = 'LOGGED_OUT' | 'LOGIN_REQUESTED' | 'LOGGED_IN';
60 | export const LOGGED_OUT = 'LOGGED_OUT';
61 | export const LOGIN_REQUESTED = 'LOGIN_REQUESTED';
62 | export const LOGGED_IN = 'LOGGED_IN';
63 |
64 | export type State = {
65 | +version: [Product, string],
66 | +productVersions: ProductVersions,
67 | +releaseInfo: ?ReleaseInfo,
68 | +checkResults: CheckResults,
69 | +pollbotVersion: ?APIVersionData,
70 | +shouldRefresh: boolean,
71 | +login: Login,
72 | +userInfo: any,
73 | +errors: Error[],
74 | };
75 |
76 | /*
77 | * action types
78 | */
79 | export const ADD_CHECK_RESULT = 'ADD_CHECK_RESULT';
80 | export const REFRESH_CHECK_RESULT = 'REFRESH_CHECK_RESULT';
81 | export const ADD_SERVER_ERROR = 'ADD_SERVER_ERROR';
82 | export const SET_VERSION = 'SET_VERSION';
83 | export const UPDATE_PRODUCT_VERSIONS = 'UPDATE_PRODUCT_VERSIONS';
84 | export const UPDATE_RELEASE_INFO = 'UPDATE_RELEASE_INFO';
85 | export const UPDATE_POLLBOT_VERSION = 'UPDATE_POLLBOT_VERSION';
86 | export const UPDATE_USER_INFO = 'UPDATE_USER_INFO';
87 |
88 | export type AddCheckResult = {|
89 | type: 'ADD_CHECK_RESULT',
90 | title: string,
91 | result: CheckResult,
92 | |};
93 | export type RefreshCheckResult = {|
94 | type: 'REFRESH_CHECK_RESULT',
95 | title: string,
96 | |};
97 | export type AddServerError = {|
98 | type: 'ADD_SERVER_ERROR',
99 | title: string,
100 | err: string,
101 | |};
102 | export type SetVersion = {|
103 | type: 'SET_VERSION',
104 | product: Product,
105 | version: string,
106 | |};
107 | export type UpdateProductVersions = {|
108 | type: 'UPDATE_PRODUCT_VERSIONS',
109 | versions: VersionsDict,
110 | product: Product,
111 | |};
112 | export type UpdateReleaseInfo = {|
113 | type: 'UPDATE_RELEASE_INFO',
114 | releaseInfo: ReleaseInfo,
115 | |};
116 | export type UpdatePollbotVersion = {|
117 | type: 'UPDATE_POLLBOT_VERSION',
118 | version: APIVersionData,
119 | |};
120 | export type LoggedIn = {|
121 | type: 'LOGGED_IN',
122 | |};
123 | export type LoggedOut = {|
124 | type: 'LOGGED_OUT',
125 | |};
126 | export type LoginRequested = {|
127 | type: 'LOGIN_REQUESTED',
128 | |};
129 | export type UpdateUserInfo = {|
130 | type: 'UPDATE_USER_INFO',
131 | userInfo: any,
132 | |};
133 |
134 | /*
135 | * saga types
136 | */
137 | export const REQUEST_ONGOING_VERSIONS = 'REQUEST_ONGOING_VERSIONS';
138 | export const REQUEST_POLLBOT_VERSION = 'REQUEST_POLLBOT_VERSION';
139 | export const UPDATE_URL = 'UPDATE_URL';
140 | export const REFRESH_STATUS = 'REFRESH_STATUS';
141 | export const REQUEST_STATUS = 'REQUEST_STATUS';
142 | export const REQUEST_LOGIN = 'REQUEST_LOGIN';
143 | export const REQUEST_LOGOUT = 'REQUEST_LOGOUT';
144 |
145 | export type RequestOngoingVersions = {|
146 | type: 'REQUEST_ONGOING_VERSIONS',
147 | |};
148 |
149 | export type RequestPollbotVersion = {|
150 | type: 'REQUEST_POLLBOT_VERSION',
151 | |};
152 |
153 | export type UpdateUrl = {|
154 | type: 'UPDATE_URL',
155 | |};
156 |
157 | export type RefreshStatus = {|
158 | type: 'REFRESH_STATUS',
159 | |};
160 |
161 | export type RequestStatus = {|
162 | type: 'REQUEST_STATUS',
163 | product: Product,
164 | version: string,
165 | |};
166 |
167 | export type RequestLogin = {|
168 | type: 'REQUEST_LOGIN',
169 | |};
170 |
171 | export type RequestLogout = {|
172 | type: 'REQUEST_LOGOUT',
173 | |};
174 |
175 | export type Action =
176 | | AddCheckResult
177 | | RefreshCheckResult
178 | | AddServerError
179 | | LoggedIn
180 | | LoggedOut
181 | | LoginRequested
182 | | RefreshStatus
183 | | RequestLogin
184 | | RequestLogout
185 | | RequestOngoingVersions
186 | | RequestPollbotVersion
187 | | RequestStatus
188 | | SetVersion
189 | | UpdateProductVersions
190 | | UpdatePollbotVersion
191 | | UpdateReleaseInfo
192 | | UpdateUrl
193 | | UpdateUserInfo;
194 |
195 | /*
196 | * Redux types
197 | */
198 | export type GetState = ReduxGetState
162 | Delivery Dashboard
163 |
164 | {/* We don't need the login button yet, and don't have an auth0 account/profile for it
165 | Firefox Releases
270 |
271 |
277 |
312 |
343 | {capitalize(product)} {version}{' '}
344 |
349 |