Skip to content

CLDR-14802++ Move Users Download into Vue add endpoint #1389

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 1 addition & 12 deletions tools/cldr-apps/js/src/esm/cldrAccount.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,9 +270,9 @@ function getHtml(json) {
html += getBulkActionMenu(json);
html += getListHiderControls(json);
}
html += getDownloadCsvForm(json);
html += getTable(json);
html += getInterestLocalesHtml(json);
html += getDownloadCsvForm(json);
if (json.exception) {
html += "<p><i>Failure: " + json.exception + "</i></p>\n";
}
Expand Down Expand Up @@ -1255,17 +1255,6 @@ function getDownloadCsvForm(json) {
if (isJustMe || !json.userPerms || !json.userPerms.canModifyUsers) {
return "";
}
// TODO: not jsp; see https://unicode-org.atlassian.net/browse/CLDR-14475
return (
"<hr />\n" +
"<form method='POST' action='DataExport.jsp'>\n" +
" <input type='hidden' name='s' value='" +
cldrStatus.getSessionId() +
"' />\n" +
" <input type='hidden' name='do' value='list' />\n" +
" <input type='submit' class='csvDownload' value='Download .csv (including LOCKED)' />\n" +
"</form>\n"
);
}

function setOnClicks() {
Expand Down
2 changes: 2 additions & 0 deletions tools/cldr-apps/js/src/specialToComponentMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import MainMenu from "./views/MainMenu.vue";
import TestPanel from "./views/TestPanel.vue";
import TransferVotes from "./views/TransferVotes.vue";
import UnknownPanel from "./views/UnknownPanel.vue";
import UserList from "./views/UserList.vue";
import VettingParticipation2 from "./views/VettingParticipation2.vue";
import VettingSummary from "./views/VettingSummary.vue";
import WaitingPanel from "./views/WaitingPanel.vue";
Expand All @@ -27,6 +28,7 @@ const specialToComponentMap = {
retry_inplace: WaitingPanel, // Like retry, but do NOT redirect after resume.
test_panel: TestPanel, // for testing
transfervotes: TransferVotes,
users: UserList,
vetting_participation2: VettingParticipation2,
vsummary: VettingSummary,
// If no match, end up here
Expand Down
31 changes: 31 additions & 0 deletions tools/cldr-apps/js/src/views/UserList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<template>
<div>
<button @click="doSpreadsheet">CLICK THE BIG BUTTON</button>
<button @click="downloadUserActivity">CLICK THE OTHER BUTTON</button>
<button @click="downloadUserActivity2">CLICK THE OTHER OTHER BUTTON</button>

<hr />
<a href="#account">Go back to the Account Page</a>
</div>
</template>

<script>
import * as cldrUserListExport from "../esm/cldrUserListExport";
import * as cldrStatus from "../esm/cldrStatus";

export default {
methods: {
doSpreadsheet() {
cldrUserListExport.downloadUserSpreadsheet();
},
downloadUserActivity() {
cldrUserListExport.downloadUserActivity(1289, cldrStatus.getSessionId());
},
downloadUserActivity2() {
cldrUserListExport.downloadUserActivity(1815, cldrStatus.getSessionId());
},
},
};
</script>

<style></style>
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@

import javax.json.bind.annotation.JsonbProperty;

import com.ibm.icu.dev.util.ElapsedTimer;
import com.ibm.icu.lang.UCharacter;

import org.apache.commons.codec.digest.DigestUtils;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.json.JSONException;
Expand All @@ -40,9 +43,7 @@
import org.unicode.cldr.util.VoteResolver;
import org.unicode.cldr.util.VoteResolver.Level;
import org.unicode.cldr.util.VoteResolver.VoterInfo;

import com.ibm.icu.dev.util.ElapsedTimer;
import com.ibm.icu.lang.UCharacter;
import org.unicode.cldr.web.api.UserItem;

/**
* This class represents the list of all registered users. It contains an inner
Expand Down Expand Up @@ -597,6 +598,22 @@ public CLDRLocale exampleLocale() {
}
return null;
}

public UserItem toUserItem() {
UserItem u = new UserItem();
u.id = this.id;
u.level = getLevel().name();
u.name = this.name;
u.org = this.org;
u.email = this.email;
u.assignedLocales = this.getAuthorizedLocaleSet().toStringArray();
if (this.last_connect != null) {
u.lastLogin = this.last_connect.toInstant();
} else {
u.lastLogin = null;
}
return u;
}
}

public static void printPasswordLink(WebContext ctx, String email, String password) {
Expand Down Expand Up @@ -993,6 +1010,40 @@ public UserRegistry.User getEmptyUser() {
public UserRegistry() {
}

/**
* Get a list of user ids for a particular org, or all.
* Anonymous users are always skipped.
* @param org org to filter on, or null for all
* @param includeLocked if true, include LOCKED users, otherwise they are omitted
* @return
* @throws SQLException
*/
public List<Integer> getUserIdList(String org, boolean includeLocked) throws SQLException {
// lifted from UserList.java
Connection conn = null;
PreparedStatement ps = null;
java.sql.ResultSet rs = null;
// List<UserList> users = new LinkedList<>();
// collect list of ids
List<Integer> ids = new LinkedList<>();
conn = DBUtils.getInstance().getAConnection();
ps = CookieSession.sm.reg.list(org, conn); // org = null to list all
rs = ps.executeQuery();
while (rs.next()) {
int level = rs.getInt(2);
if (level == UserRegistry.ANONYMOUS) {
continue;
}
if (!includeLocked
&& level >= UserRegistry.LOCKED) {
continue;
}
int id = rs.getInt(1);
ids.add(id);
}
return ids;
}

// ------- special things for "list" mode:

public java.sql.PreparedStatement list(String organization, Connection conn) throws SQLException {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.unicode.cldr.web.api;

import java.time.Instant;

/**
* This class exists because UserRegistry.User is unserializable
* at present.
*/
public class UserItem implements Comparable<UserItem> {
public String level;
public String name;
public String org;
public int id;
public String email;
public String assignedLocales[];
public Instant lastLogin;

/**
* Order by org, name, id
*/
@Override
public int compareTo(UserItem o) {
int rc = org.compareTo(o.org);
if (rc == 0) {
rc = name.compareTo(o.name);
}
if (rc == 0) {
rc = Integer.compare(id, o.id);
}
return rc;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import org.unicode.cldr.web.UserRegistry;

@Path("/userlevels")
@Tag(name = "userlevels", description = "Get the list of Survey Tool user levels")
@Tag(name = "user", description = "APIs for user management")
public class UserLevels {

@GET
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package org.unicode.cldr.web.api;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Logger;

import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
import org.unicode.cldr.web.CookieSession;
import org.unicode.cldr.web.DBUtils;
import org.unicode.cldr.web.SurveyLog;
import org.unicode.cldr.web.UserRegistry;

@Path("/users")
@Tag(name = "user", description = "APIs for user management")
public class UsersAPI {
private static final Logger logger = SurveyLog.forClass(UsersAPI.class);
@GET
@Produces(MediaType.APPLICATION_JSON)
@Operation(
summary = "List Users",
description = "List users, optionally filtered by organization")
@APIResponses(
value = {
@APIResponse(
responseCode = "200",
description = "User list",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = UserList.class))
)
}
)
public Response listUsers(
@HeaderParam(Auth.SESSION_HEADER) String session,
@QueryParam("org") @Schema(required = false, description = "Organization Filter (ignored unless admin)", example = "Guest") String org,
@QueryParam("includeLocked") @Schema(required = false, description = "True to include locked users", defaultValue = "false", example = "false") boolean includeLocked
) {
// boilerplate: login
final CookieSession mySession = Auth.getSession(session);
if (mySession == null) {
return Auth.noSessionResponse();
}
if (mySession.user == null || !UserRegistry.userCanCreateUsers(mySession.user)) {
return Response.status(Status.UNAUTHORIZED).build();
}

if (!UserRegistry.userIsAdmin(mySession.user)) {
// non-admins can only list their own org
org = mySession.user.org;
}
// collect list of ids
List<Integer> ids = null;
try {
ids = CookieSession.sm.reg.getUserIdList(org, includeLocked);
} catch(SQLException se) {
logger.log(java.util.logging.Level.WARNING,
"Query for org " + org + " failed: " + DBUtils.unchainSqlException(se), se);
return Response.status(Status.INTERNAL_SERVER_ERROR).entity(new STError(se)).build();
}
// now collect the actual users
Set<UserItem> users = new TreeSet<>();
for (final int id : ids) {
UserItem u = CookieSession.sm.reg.getInfo(id).toUserItem();
users.add(u);
}

return Response.ok(new UserList(users)).build();
}

public static final class UserList {
UserList(Collection<UserItem> c) {
users = c.toArray(new UserItem[c.size()]);
}
public UserItem users[];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.unicode.cldr.util;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;

import java.util.Collections;

import com.google.common.collect.ImmutableSet;

import org.junit.jupiter.api.Test;

public class LocaleSetTest {
@Test
public void testToString() {
final String[] ALL_LOCALES = { "*" };
assertArrayEquals(ALL_LOCALES, new LocaleSet(true).toStringArray(), "for all-locales set");

final String[] NO_LOCALES = {};
assertArrayEquals(NO_LOCALES, new LocaleSet().toStringArray(), "for no-locales set");
assertArrayEquals(NO_LOCALES, new LocaleSet(Collections.emptySet()).toStringArray(), "for empty set");

final String[] ONE_LOCALE = { "tlh" };
assertArrayEquals(ONE_LOCALE, new LocaleSet(ImmutableSet.of("tlh")).toStringArray(), "for one-locale set");
}
}
22 changes: 22 additions & 0 deletions tools/cldr-code/src/main/java/org/unicode/cldr/util/LocaleSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,26 @@ public Set<CLDRLocale> getSet() {
return set;
}

private String[] getAllStringArray() {
String s[] = new String[1];
s[0] = "*";
return s;
}

/**
* Return the LocaleSet as an array of Strings
* (for API use). Returns {"*"} for allLocales.
* @return
*/
public String[] toStringArray() {
if (isAllLocales) {
return getAllStringArray();
} else {
Set<String> str = new TreeSet<>();
for (final CLDRLocale l : set) {
str.add(l.getBaseName());
}
return str.toArray(new String[str.size()]);
}
}
}