File Coverage

blib/lib/Plack/App/GraphQL/UITemplate.pm
Criterion Covered Total %
statement 6 25 24.0
branch 0 2 0.0
condition n/a
subroutine 2 8 25.0
pod 0 4 0.0
total 8 39 20.5


line stmt bran cond sub pod time code
1             package Plack::App::GraphQL::UITemplate;
2              
3 1     1   1516 use Moo;
  1         3  
  1         9  
4 1     1   355 use Plack::Util;
  1         1  
  1         8220  
5              
6             has tt => (
7             is => 'ro',
8             required => 1,
9             builder => '_build_tt',
10             handles => {
11             tt_process => 'process',
12             }
13             );
14              
15             sub _build_tt {
16 0     0     return Plack::Util::load_class('Template::Tiny')->new();
17             }
18              
19             has template => (
20             is => 'ro',
21             required => 1,
22             builder => '_build_template',
23             );
24              
25             sub _build_template {
26 0     0     $/ = undef;
27 0           return my $data = <DATA>;
28             }
29              
30             has json_encoder => (
31             is => 'ro',
32             required => 1,
33             handles => {
34             json_encode => 'encode',
35             },
36             );
37              
38             sub safe_serialize {
39 0     0 0   my ($self, $data) = @_;
40 0 0         if($data) {
41 0           my $json = $self->json_encode($data);
42 0           $json =~ s#/#\\/#g;
43 0           return $json;
44             } else {
45 0           return 'undefined';
46             }
47             }
48              
49             sub process {
50 0     0 0   my ($self, $req) = @_;
51 0           my $query = $req->query_parameters;
52 0           my %args = $self->args_from_query($query);
53 0           return my $body = $self->process_args(%args);
54             }
55              
56             sub args_from_query {
57 0     0 0   my ($self, $query) = @_;
58             return my %args = (
59             graphiql_version => 'latest',
60             queryString => $self->safe_serialize( $query->{'query'} ),
61             operationName => $self->safe_serialize( $query->{'operationName'} ),
62             resultString => $self->safe_serialize( $query->{'result'} ),
63 0           variablesString => $self->safe_serialize( $query->{'variables'} ),
64             );
65             }
66              
67             sub process_args {
68 0     0 0   my ($self, %args) = @_;
69 0           my $input = $self->template;
70 0           $self->tt_process(\$input, \%args, \my $output);
71 0           return $output;
72             }
73              
74             1;
75              
76             =head1 NAME
77            
78             Plack::App::GraphQL::UITemplate - Template and processing for the GraphQL UI
79              
80             =head1 SYNOPSIS
81            
82             There's nothing really for end users here. Its just refactored into its own
83             package for code organization purposes.
84              
85             =head1 DESCRIPTION
86              
87             This is a package used to prepare and return an HTML response when you have the
88             'ui' flag enabled (probably for development) and the client requests an HTML
89             response. This is based on L<https://github.com/graphql/graphiql>
90              
91             Feel free to make your own improved development / query interface and put it on
92             CPAN!
93              
94             =head1 AUTHOR
95            
96             John Napiorkowski
97              
98             =head1 SEE ALSO
99            
100             L<Plack::App::GraphQL>
101            
102             =cut
103              
104             __DATA__
105              
106             <!DOCTYPE html>
107             <html>
108             <head>
109             <meta charset="utf-8" />
110             <title>GraphiQL</title>
111             <meta name="robots" content="noindex" />
112             <style>
113             html, body {
114             height: 100%;
115             margin: 0;
116             overflow: hidden;
117             width: 100%;
118             }
119             </style>
120             <link href="//cdn.jsdelivr.net/npm/graphiql@[% graphiql_version %]/graphiql.css" rel="stylesheet" />
121             <script src="//cdn.jsdelivr.net/fetch/0.9.0/fetch.min.js"></script>
122             <script src="//cdn.jsdelivr.net/react/15.4.2/react.min.js"></script>
123             <script src="//cdn.jsdelivr.net/react/15.4.2/react-dom.min.js"></script>
124             <script src="//cdn.jsdelivr.net/npm/graphiql@[% graphiql_version %]/graphiql.min.js"></script>
125             </head>
126             <body>
127             <script>
128             // Collect the URL parameters
129             var parameters = {};
130             window.location.search.substr(1).split('&').forEach(function (entry) {
131             var eq = entry.indexOf('=');
132             if (eq >= 0) {
133             parameters[decodeURIComponent(entry.slice(0, eq))] =
134             decodeURIComponent(entry.slice(eq + 1));
135             }
136             });
137             // Produce a Location query string from a parameter object.
138             function locationQuery(params) {
139             return '?' + Object.keys(params).filter(function (key) {
140             return Boolean(params[key]);
141             }).map(function (key) {
142             return encodeURIComponent(key) + '=' +
143             encodeURIComponent(params[key]);
144             }).join('&');
145             }
146             // Derive a fetch URL from the current URL, sans the GraphQL parameters.
147             var graphqlParamNames = {
148             query: true,
149             variables: true,
150             operationName: true
151             };
152             var otherParams = {};
153             for (var k in parameters) {
154             if (parameters.hasOwnProperty(k) && graphqlParamNames[k] !== true) {
155             otherParams[k] = parameters[k];
156             }
157             }
158             var fetchURL = locationQuery(otherParams);
159             // Defines a GraphQL fetcher using the fetch API.
160             function graphQLFetcher(graphQLParams) {
161             return fetch(fetchURL, {
162             method: 'post',
163             headers: {
164             'Accept': 'application/json',
165             'Content-Type': 'application/json'
166             },
167             body: JSON.stringify(graphQLParams),
168             credentials: 'include',
169             }).then(function (response) {
170             return response.text();
171             }).then(function (responseBody) {
172             try {
173             return JSON.parse(responseBody);
174             } catch (error) {
175             return responseBody;
176             }
177             });
178             }
179             // When the query and variables string is edited, update the URL bar so
180             // that it can be easily shared.
181             function onEditQuery(newQuery) {
182             parameters.query = newQuery;
183             updateURL();
184             }
185             function onEditVariables(newVariables) {
186             parameters.variables = newVariables;
187             updateURL();
188             }
189             function onEditOperationName(newOperationName) {
190             parameters.operationName = newOperationName;
191             updateURL();
192             }
193             function updateURL() {
194             history.replaceState(null, null, locationQuery(parameters));
195             }
196             // Render <GraphiQL /> into the body.
197             ReactDOM.render(
198             React.createElement(GraphiQL, {
199             fetcher: graphQLFetcher,
200             onEditQuery: onEditQuery,
201             onEditVariables: onEditVariables,
202             onEditOperationName: onEditOperationName,
203             query: [% queryString %],
204             response: [% resultString %],
205             variables: [% variablesString %],
206             operationName: [% operationName %],
207             }),
208             document.body
209             );
210             </script>
211             </body>
212             </html>
213