• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1<%@ page import="com.google.appengine.repackaged.com.google.gson.Gson" %>
2<%@ page import="com.google.common.collect.ImmutableList" %>
3<%@ page import="com.google.phonenumbers.ServletMain" %>
4<%@ page import="com.google.phonenumbers.migrator.MigrationEntry" %>
5<%@ page import="com.google.phonenumbers.migrator.MigrationResult" %>
6<!DOCTYPE html>
7<%@ page contentType="text/html;charset=UTF-8" language="java" %>
8<%
9  final String E164_NUMBERS_LINK = "https://support.twilio.com/hc/en-us/articles/223183008-Formatting-International-Phone-Numbers";
10  final String COUNTRY_CODE_LINK = "https://countrycode.org/";
11  // TODO: use documentation link from base repository when forked repository has been merged in
12  final String DOCUMENTATION_LINK = "https://github.com/TomiwaOke/libphonenumber/tree/master/migrator/README.md";
13  final String ISSUE_TRACKER_LINK = "https://issuetracker.google.com/issues/new?component=192347";
14  final String GUIDELINES_LINK = "https://github.com/google/libphonenumber/blob/master/CONTRIBUTING.md#filing-a-code-issue";
15
16  final Gson gson = new Gson();
17  ImmutableList<MigrationResult> validMigrations = (ImmutableList<MigrationResult>) request.getAttribute("validMigrations");
18  ImmutableList<MigrationResult> invalidMigrations = (ImmutableList<MigrationResult>) request.getAttribute("invalidMigrations");
19  ImmutableList<MigrationEntry> validUntouchedNums = (ImmutableList<MigrationEntry>) request.getAttribute("validUntouchedNumbers");
20  ImmutableList<MigrationEntry> invalidUntouchedNums = (ImmutableList<MigrationEntry>) request.getAttribute("invalidUntouchedNumbers");
21%>
22<html>
23<head>
24  <link type="text/css" rel="stylesheet" href="/stylesheets/servlet-main.css" />
25  <title>Migrator</title>
26  <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
27  <script type="text/javascript">
28    const VALID_MIGRATIONS = 'Valid Migrations';
29    const INVALID_MIGRATIONS = 'Invalid Migrations';
30    const UNTOUCHED_VALID = 'Already Valid Numbers';
31    const UNTOUCHED_INVALID = 'Invalid Non-migratable Numbers';
32
33    const CHART_DESCRIPTIONS = new Map();
34    CHART_DESCRIPTIONS[VALID_MIGRATIONS] = 'The following are numbers that were successfully migrated by the tool:';
35    CHART_DESCRIPTIONS[INVALID_MIGRATIONS] = 'The following are numbers that were migrated by the tool but were not able' +
36            ' to be verified as valid numbers based on metadata for the given country code:';
37    CHART_DESCRIPTIONS[UNTOUCHED_VALID] = 'The following are numbers that were already in valid formats:';
38    CHART_DESCRIPTIONS[UNTOUCHED_INVALID] = 'The following numbers were not seen as valid and could not be migrated based' +
39            ' on the given country code:';
40
41    function getNumbersForSegment(selection) {
42      if (selection === VALID_MIGRATIONS) {
43        return <%=gson.toJson(ServletMain.getMigrationResultOutputList(validMigrations))%>;
44      } else if (selection === INVALID_MIGRATIONS) {
45        return <%=gson.toJson(ServletMain.getMigrationResultOutputList(invalidMigrations))%>;
46      } else if (selection === UNTOUCHED_VALID) {
47        return <%=gson.toJson(ServletMain.getMigrationEntryOutputList(validUntouchedNums))%>;
48      }
49      return <%=gson.toJson(ServletMain.getMigrationEntryOutputList(invalidUntouchedNums))%>;
50    }
51
52    google.charts.load('current', {packages:['corechart']});
53    google.charts.setOnLoadCallback(drawChart);
54    function drawChart() {
55      const chartData = google.visualization.arrayToDataTable([
56        ['Task', 'Frequency'],
57        [VALID_MIGRATIONS, <%= validMigrations != null ? validMigrations.size() : 0%>],
58        [INVALID_MIGRATIONS, <%= invalidMigrations != null ? invalidMigrations.size() : 0%>],
59        [UNTOUCHED_VALID, <%= validUntouchedNums != null ? validUntouchedNums.size() : 0%>],
60        [UNTOUCHED_INVALID, <%= invalidUntouchedNums != null ? invalidUntouchedNums.size() : 0%>]
61      ]);
62
63      const chartProperties = {
64        pieHole: 0.4,
65        chartArea: { width: '90%', height: '100%' },
66        colors: [
67          <%=validMigrations != null && !validMigrations.isEmpty()%> ? '#277301' : '',
68          <%=invalidMigrations != null && !invalidMigrations.isEmpty()%> ? '#ffbf36' : '',
69          <%=validUntouchedNums != null && !validUntouchedNums.isEmpty()%> ? '#90ee90' : '',
70          <%=invalidUntouchedNums != null && !invalidUntouchedNums.isEmpty()%> ? '#ff472b' : '']
71      };
72
73      const modalBackdrop = document.getElementById("modalBackdrop");
74
75      document.getElementById("modalButton").onclick = function() {
76        document.getElementById("numbersList").innerHTML = '';
77        modalBackdrop.style.display = 'none';
78      };
79
80      window.onclick = function(event) {
81        if (event.target === modalBackdrop) {
82          document.getElementById("numbersList").innerHTML = '';
83          modalBackdrop.style.display = 'none';
84        }
85      };
86
87      function onSegmentClick() {
88        const selection = chart.getSelection()[0];
89        if (selection) {
90          const selectionName = chartData.getValue(selection.row, 0);
91          const numbersList = document.getElementById("numbersList");
92          const segmentNumbers = getNumbersForSegment(selectionName);
93
94          segmentNumbers.forEach(number => {
95            const value = document.createElement('li');
96            value.appendChild(document.createTextNode(number));
97            numbersList.appendChild(value);
98          });
99          document.getElementById("modalTitle").innerHTML = selectionName;
100          document.getElementById("modalDescription").innerHTML = CHART_DESCRIPTIONS[selectionName];
101          modalBackdrop.style.display = 'block';
102        }
103      }
104
105      const chart = new google.visualization.PieChart(document.getElementById('migration-chart'));
106      google.visualization.events.addListener(chart, 'select', onSegmentClick);
107      chart.draw(chartData, chartProperties);
108    }
109  </script>
110</head>
111<body>
112  <div class="page-heading">
113    <h1>Phone Number Migrator</h1>
114    <p>
115      The migrator is a tool which takes in a given <a href="<%=E164_NUMBERS_LINK%>" target="_blank">E.164 phone number(s)</a>
116      input as well as the corresponding BCP-47 <a href="<%=COUNTRY_CODE_LINK%>" target="_blank">country code</a>. The tool
117      will then check the validity of the phone number based on the country code and if possible, will convert the number
118      into a valid, dialable format.
119    </p>
120    <p>The following are the two available migration types that can be performed:</p>
121    <ul>
122      <li>
123        <strong>Single Number Migration:</strong> input a single E.164 phone number with its corresponding BCP-47 country
124        code. If there is an available migration that can be performed on the number, it will be converted to the new
125        format based on the specified migration rules.<br><br>
126      </li>
127      <li>
128        <strong>File Migration:</strong> input a text file containing one E.164 number per line along with the BCP-47
129        country code that corresponds to the numbers in the text file. All numbers in the text file that match available
130        migrations will be migrated and there will be the option of downloading a new text file containing the updated numbers.
131        By default, invalid migrations and numbers that did not go through a process of migration will be written to file
132        in their original text file format.
133        <br><br>
134      </li>
135    </ul>
136    <p>
137      For more information on the capabilities of the migrator as well as instructions on how to install the command line
138      tool, please view the <a href="<%=DOCUMENTATION_LINK%>" target="_blank">documentation</a>.
139    </p>
140  </div>
141
142  <div class="migration-result">
143    <%
144      if (request.getAttribute("numberError") == null && request.getAttribute("number") != null) {
145        if (request.getAttribute("validMigration") != null) {
146          out.print("<h3 class='valid'>Valid +" + request.getAttribute("numberCountryCode") + " Phone Number Produced!</h3>");
147          out.print("<p>The stale number '" + request.getAttribute("number") + "' was successfully migrated into the" +
148                  " phone number: +" + request.getAttribute("validMigration") + "</p>");
149        } else if (request.getAttribute("invalidMigration") != null) {
150          out.print("<h3 class='invalid-migration'>Invalid +" + request.getAttribute("numberCountryCode") + " Migration</h3>");
151          out.print("<p>The stale number '" + request.getAttribute("number") + "' was migrated into the phone number:" +
152                  " +" + request.getAttribute("invalidMigration") + ". However this was not seen as valid using our internal" +
153                  " metadata for country code +" + request.getAttribute("numberCountryCode") + ".</p>");
154        } else if (request.getAttribute("alreadyValidNumber") != null) {
155          out.print("<h3 class='valid'>Already Valid +" + request.getAttribute("numberCountryCode") + " Phone Number!</h3>");
156          out.print("<p>The entered phone number was already seen as being in a valid, dialable format based on our" +
157                  " metadata for country code +" + request.getAttribute("numberCountryCode") + ". Here is the number in" +
158                  " its clean E.164 format: +" + request.getAttribute("alreadyValidNumber") + "</p>");
159        } else {
160          out.print("<h3 class='invalid-number'>Non-migratable +" + request.getAttribute("numberCountryCode") + " Phone Number</h3>");
161          out.print("<p>The phone number '" + request.getAttribute("number") + "' was not seen as a valid number and" +
162                  " no migration recipe could be found for country code +" + request.getAttribute("numberCountryCode") +
163                  " to migrate it. This may be because you have entered a country code which does not correctly correspond" +
164                  " to the given phone number or the specified number has never been valid.</p>");
165        }
166        out.print("<p style='color: red; font-size: 14px'>Think there's an issue? File one <a href='" + ISSUE_TRACKER_LINK +
167                "' target='_blank'>here</a> following the given <a href='" + GUIDELINES_LINK + "' target='_blank'>guidelines</a>.</p>");
168      } else if (request.getAttribute("fileError") == null && request.getAttribute("fileName") != null) {
169        out.print("<h3>'" + request.getAttribute("fileName") + "' Migration Report for Country Code: +" + request.getAttribute("fileCountryCode") + "</h3>");
170        out.print("<p>Below is a chart showing the ratio of numbers from the entered file that were able to be migrated" +
171                " using '+" + request.getAttribute("fileCountryCode") + "' migration recipes. To understand more," +
172                " select a given segment from the chart below.</p>");
173        out.print("<div class='chart-wrap'><div id='migration-chart' class='chart'></div></div>");
174
175        out.print("<form action='" + request.getContextPath() + "/migrate' method='get' style='margin-bottom: 1rem'>");
176        out.print("<input type='hidden' name='countryCode' value='" + request.getAttribute("fileCountryCode") + "'/>");
177        out.print("<input type='hidden' name='fileName' value='" + request.getAttribute("fileName") + "'/>");
178        out.print("<input type='hidden' name='fileContent' value='" + request.getAttribute("fileContent") + "'/>");
179        out.print("<input type='submit' value='Export Results' class='button'/>");
180        out.print("</form>");
181      }
182    %>
183  </div>
184
185  <div class="migration-forms">
186    <div class="migration-form">
187      <h3>Single Number Migration</h3>
188      <div class="error-message"><%=request.getAttribute("numberError") == null ? "" : request.getAttribute("numberError")%></div>
189      <form action="${pageContext.request.contextPath}/migrate" method="post" enctype="multipart/form-data">
190        <label for="number">Phone number:</label>
191        <p>Enter a phone number in E.164 format. Inputted numbers can include spaces, curved brackets and hyphens</p>
192        <input type="text" name="number" id="number" placeholder="+841205555555" required
193               value="<%=request.getAttribute("number") == null ? "" : request.getAttribute("number")%>"/>
194
195        <label for="numberCountryCode">Country Code:</label>
196        <p>Enter the BCP-47 country code in which the specified E.164 phone number belongs to</p>
197        <input type="number" name="numberCountryCode" id="numberCountryCode" placeholder="84" required
198               value="<%=request.getAttribute("numberCountryCode") == null ? "" : request.getAttribute("numberCountryCode")%>"/>
199
200        <label for="numberCustomRecipe">Custom Recipe:</label>
201        <p>
202          (Optional) Upload a csv file containing a custom recipes table to be used for migrations. To understand how to
203          create a custom recipe file, please view the <a href="<%=DOCUMENTATION_LINK%>" target="_blank">documentation</a>.
204        </p>
205        <input type="file" name="customRecipe" id="numberCustomRecipe" accept=".csv"/>
206
207        <input type="submit" value="Migrate Number" class="button"/>
208      </form>
209    </div>
210
211    <div class="migration-form">
212      <h3>File Migration</h3>
213      <div class="error-message"><%=request.getAttribute("fileError") == null ? "" : request.getAttribute("fileError")%></div>
214      <form action="${pageContext.request.contextPath}/migrate" method="post" enctype="multipart/form-data">
215        <label for="file">File:</label>
216        <p>Upload a file containing one E.164 phone number per line. Numbers can include spaces, curved brackets and hyphens</p>
217        <input type="file" name="file" id="file" accept="text/plain" required/>
218
219        <label for="fileCountryCode">Country Code:</label>
220        <p>Enter the BCP-47 country code in which the E.164 phone numbers from the specified file belong to</p>
221        <input type="number" name="fileCountryCode" id="fileCountryCode" placeholder="84" required/>
222
223        <label for="fileCustomRecipe">Custom Recipe:</label>
224        <p>
225          (Optional) Upload a csv file containing a custom recipes table to be used for migrations. To understand how to
226          create a custom recipe file, please view the <a href="<%=DOCUMENTATION_LINK%>" target="_blank">documentation</a>.
227        </p>
228        <input type="file" name="customRecipe" id="fileCustomRecipe" accept=".csv"/>
229
230        <input type="submit" value="Migrate File" class="button"/>
231      </form>
232    </div>
233  </div>
234
235  <div id="modalBackdrop" class="modal-backdrop">
236    <div class="modal-content">
237      <h3 id="modalTitle"></h3>
238      <p id="modalDescription" style="color: grey; font-size: 12px"></p>
239      <div class="body">
240        <ul id="numbersList" style="padding-left: 1.5rem"></ul>
241        <p style="color: red; font-size: 14px">
242          Think there's an issue? File one <a href="<%=ISSUE_TRACKER_LINK%>" target="_blank">here</a>
243          following the given <a href="<%=GUIDELINES_LINK%>" target="_blank">guidelines</a>.
244        </p>
245      </div>
246      <button id="modalButton" class="button">Close</button>
247    </div>
248  </div>
249</body>
250</html>
251