Compare commits

..

30 Commits

Author SHA1 Message Date
Avinash Lingaloo
2a4b61b9c7 iteration 1 2023-02-01 12:39:56 +04:00
Avinash Lingaloo
2da4f45a31 Merge branch 'main' into mm-44652-channel-notification-setting 2023-01-30 14:48:45 +04:00
Avinash Lingaloo
da3f396f5b chore: i18n extract 2023-01-30 14:41:56 +04:00
Avinash Lingaloo
5d94ccf06b feat: update channel notification pref locally 2023-01-27 19:37:28 +04:00
Avinash Lingaloo
1b5b1b4591 feat: api called made to server 2023-01-27 19:18:09 +04:00
Avinash Lingaloo
b2cbe8b570 Update channel_notification_preference.tsx 2023-01-27 18:50:08 +04:00
Avinash Lingaloo
3176c2fed5 wip 2023-01-27 18:40:29 +04:00
Avinash Lingaloo
b27c72e965 Update notification_preference.tsx 2023-01-27 17:38:20 +04:00
Avinash Lingaloo
7b1a4d6275 Merge branch 'main' into mm-44652-channel-notification-setting 2023-01-27 16:58:55 +04:00
Avinash Lingaloo
701c010bdf feat: saving to server 2023-01-27 16:58:24 +04:00
Avinash Lingaloo
e5db26ed35 Update channel_notification_preference.tsx 2023-01-27 15:07:40 +04:00
Avinash Lingaloo
9d34726bc3 Update channel_notification_preference.tsx 2023-01-27 14:48:35 +04:00
Avinash Lingaloo
8581765196 feat: toggle channel unmute 2023-01-27 13:56:14 +04:00
Avinash Lingaloo
9f3e35a9bc feat: include muted flag
feat: include muted flag
2023-01-26 14:53:57 +04:00
Avinash Lingaloo
89387bf36b feat: channelInfo - rhs info now shows global default notif pref 2023-01-26 14:36:31 +04:00
Avinash Lingaloo
f1261535b7 feat: including global default into the selection 2023-01-26 14:23:24 +04:00
Avinash Lingaloo
dde547a919 feat: only show Thread Replies if on Mention 2023-01-25 15:02:24 +04:00
Avinash Lingaloo
b46ba9aa5d fix: onPress for resetToDefault 2023-01-25 14:55:33 +04:00
Avinash Lingaloo
d9b4d18d69 feat: wiring up channel_notification_preference
Update channel_notification_preference.tsx
2023-01-25 14:14:46 +04:00
Avinash Lingaloo
cae2b818f3 Merge branch 'main' into mm-44652-channel-notification-setting 2023-01-25 10:22:10 +04:00
Avinash Lingaloo
3a56209562 feat: positioning reset to default 2023-01-20 17:09:06 +04:00
Avinash Lingaloo
eef1e90bf2 feat: includes 'reset to default' 2023-01-20 16:54:42 +04:00
Avinash Lingaloo
aa814243b5 fix: remove duplicate text 2023-01-20 15:09:22 +04:00
Avinash Lingaloo
fc16af723a Update channel_notification_preference.tsx 2023-01-20 15:05:19 +04:00
Avinash Lingaloo
273f098bee Merge branch 'main' into mm-44652-channel-notification-setting 2023-01-20 11:54:22 +04:00
Avinash Lingaloo
385105b621 feat: channel notification preference screen - UI part 2 2023-01-11 12:16:21 +04:00
Avinash Lingaloo
bbcbca1675 feat: channel notification preference screen - UI part 1 2023-01-11 12:04:44 +04:00
Avinash Lingaloo
898942b60d fix: the line separator would appear multiple times depending on the selected option. 2023-01-10 13:19:17 +04:00
Avinash Lingaloo
461bbbbfad chore: registers Screens.CHANNEL_MENTION 2023-01-10 11:01:03 +04:00
Avinash Lingaloo
ae0689a05b chore: removes Screens.CHANNEL_MENTION from not_implemented list 2023-01-10 10:59:56 +04:00
237 changed files with 5927 additions and 8171 deletions

View File

@@ -1,4 +1,4 @@
Submit feature requests to https://mattermost.com/suggestions/. File non-security related bugs here in the following format:
Submit feature requests to https://portal.productboard.com/mattermost/33-what-matters-to-you. File non-security related bugs here in the following format:
#### Summary
Issue in one concise sentence.

View File

@@ -112,7 +112,7 @@ android {
applicationId "com.mattermost.rnbeta"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 455
versionCode 454
versionName "2.0.0"
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
@@ -147,7 +147,6 @@ android {
release {
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro"
if (useReleaseKey) {
signingConfig signingConfigs.release
} else {

View File

@@ -1,28 +0,0 @@
package com.mattermost.helpers
import android.graphics.Bitmap
import android.util.LruCache
class BitmapCache {
private var memoryCache: LruCache<String, Bitmap>
init {
val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
val cacheSize = maxMemory / 8
memoryCache = object : LruCache<String, Bitmap>(cacheSize) {
override fun sizeOf(key: String, bitmap: Bitmap): Int {
return bitmap.byteCount / 1024
}
}
}
fun getBitmapFromMemCache(key: String): Bitmap? {
return memoryCache.get(key)
}
fun addBitmapToMemoryCache(key: String, bitmap: Bitmap) {
if (getBitmapFromMemCache(key) == null) {
memoryCache.put(key, bitmap)
}
}
}

View File

@@ -7,6 +7,7 @@ import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
@@ -27,6 +28,7 @@ import androidx.core.app.Person;
import androidx.core.app.RemoteInput;
import androidx.core.graphics.drawable.IconCompat;
import com.facebook.react.bridge.ReactApplicationContext;
import com.mattermost.rnbeta.*;
import java.io.IOException;
@@ -51,11 +53,7 @@ public class CustomPushNotificationHelper {
private static NotificationChannel mHighImportanceChannel;
private static NotificationChannel mMinImportanceChannel;
private static final OkHttpClient client = new OkHttpClient();
private static final BitmapCache bitmapCache = new BitmapCache();
private static void addMessagingStyleMessages(NotificationCompat.MessagingStyle messagingStyle, String conversationTitle, Bundle bundle) {
private static void addMessagingStyleMessages(Context context, NotificationCompat.MessagingStyle messagingStyle, String conversationTitle, Bundle bundle) {
String message = bundle.getString("message", bundle.getString("body"));
String senderId = bundle.getString("sender_id");
String serverUrl = bundle.getString("server_url");
@@ -77,7 +75,7 @@ public class CustomPushNotificationHelper {
if (serverUrl != null && !type.equals(CustomPushNotificationHelper.PUSH_TYPE_SESSION)) {
try {
Bitmap avatar = userAvatar(serverUrl, senderId, urlOverride);
Bitmap avatar = userAvatar(context, serverUrl, senderId, urlOverride);
if (avatar != null) {
sender.setIcon(IconCompat.createWithBitmap(avatar));
}
@@ -179,12 +177,12 @@ public class CustomPushNotificationHelper {
String groupId = is_crt_enabled && !android.text.TextUtils.isEmpty(rootId) ? rootId : channelId;
addNotificationExtras(notification, bundle);
setNotificationIcons(notification, bundle);
setNotificationMessagingStyle(notification, bundle);
setNotificationIcons(context, notification, bundle);
setNotificationMessagingStyle(context, notification, bundle);
setNotificationGroup(notification, groupId, createSummary);
setNotificationBadgeType(notification);
setNotificationChannel(context, notification);
setNotificationChannel(context, notification, bundle);
setNotificationDeleteIntent(context, notification, bundle, notificationId);
addNotificationReplyAction(context, notification, bundle, notificationId);
@@ -256,7 +254,15 @@ public class CustomPushNotificationHelper {
return title;
}
private static NotificationCompat.MessagingStyle getMessagingStyle(Bundle bundle) {
private static int getIconResourceId(Context context, String iconName) {
final Resources res = context.getResources();
String packageName = context.getPackageName();
String defType = "mipmap";
return res.getIdentifier(iconName, defType, packageName);
}
private static NotificationCompat.MessagingStyle getMessagingStyle(Context context, Bundle bundle) {
NotificationCompat.MessagingStyle messagingStyle;
final String senderId = "me";
final String serverUrl = bundle.getString("server_url");
@@ -269,7 +275,7 @@ public class CustomPushNotificationHelper {
if (serverUrl != null && !type.equals(CustomPushNotificationHelper.PUSH_TYPE_SESSION)) {
try {
Bitmap avatar = userAvatar(serverUrl, "me", urlOverride);
Bitmap avatar = userAvatar(context, serverUrl, "me", urlOverride);
if (avatar != null) {
sender.setIcon(IconCompat.createWithBitmap(avatar));
}
@@ -282,7 +288,7 @@ public class CustomPushNotificationHelper {
String conversationTitle = getConversationTitle(bundle);
setMessagingStyleConversationTitle(messagingStyle, conversationTitle, bundle);
addMessagingStyleMessages(messagingStyle, conversationTitle, bundle);
addMessagingStyleMessages(context, messagingStyle, conversationTitle, bundle);
return messagingStyle;
}
@@ -309,6 +315,25 @@ public class CustomPushNotificationHelper {
return getConversationTitle(bundle);
}
public static int getSmallIconResourceId(Context context, String iconName) {
if (iconName == null) {
iconName = "ic_notification";
}
int resourceId = getIconResourceId(context, iconName);
if (resourceId == 0) {
iconName = "ic_launcher";
resourceId = getIconResourceId(context, iconName);
if (resourceId == 0) {
resourceId = android.R.drawable.ic_dialog_info;
}
}
return resourceId;
}
private static String removeSenderNameFromMessage(String message, String senderName) {
int index = message.indexOf(senderName);
if (index == 0) {
@@ -340,7 +365,7 @@ public class CustomPushNotificationHelper {
}
}
private static void setNotificationChannel(Context context, NotificationCompat.Builder notification) {
private static void setNotificationChannel(Context context, NotificationCompat.Builder notification, Bundle bundle) {
// If Android Oreo or above we need to register a channel
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
@@ -364,8 +389,8 @@ public class CustomPushNotificationHelper {
notification.setDeleteIntent(deleteIntent);
}
private static void setNotificationMessagingStyle(NotificationCompat.Builder notification, Bundle bundle) {
NotificationCompat.MessagingStyle messagingStyle = getMessagingStyle(bundle);
private static void setNotificationMessagingStyle(Context context, NotificationCompat.Builder notification, Bundle bundle) {
NotificationCompat.MessagingStyle messagingStyle = getMessagingStyle(context, bundle);
notification.setStyle(messagingStyle);
}
@@ -378,18 +403,20 @@ public class CustomPushNotificationHelper {
}
}
private static void setNotificationIcons(NotificationCompat.Builder notification, Bundle bundle) {
private static void setNotificationIcons(Context context, NotificationCompat.Builder notification, Bundle bundle) {
String smallIcon = bundle.getString("smallIcon");
String channelName = getConversationTitle(bundle);
String senderName = bundle.getString("sender_name");
String serverUrl = bundle.getString("server_url");
String urlOverride = bundle.getString("override_icon_url");
notification.setSmallIcon(R.mipmap.ic_notification);
int smallIconResId = getSmallIconResourceId(context, smallIcon);
notification.setSmallIcon(smallIconResId);
if (serverUrl != null && channelName.equals(senderName)) {
try {
String senderId = bundle.getString("sender_id");
Bitmap avatar = userAvatar(serverUrl, senderId, urlOverride);
Bitmap avatar = userAvatar(context, serverUrl, senderId, urlOverride);
if (avatar != null) {
notification.setLargeIcon(avatar);
}
@@ -399,31 +426,29 @@ public class CustomPushNotificationHelper {
}
}
private static Bitmap userAvatar(final String serverUrl, final String userId, final String urlOverride) throws IOException {
private static Bitmap userAvatar(Context context, final String serverUrl, final String userId, final String urlOverride) throws IOException {
try {
Response response;
final OkHttpClient client = new OkHttpClient();
Request request;
String url;
if (!TextUtils.isEmpty(urlOverride)) {
Request request = new Request.Builder().url(urlOverride).build();
request = new Request.Builder().url(urlOverride).build();
Log.i("ReactNative", String.format("Fetch override profile image %s", urlOverride));
response = client.newCall(request).execute();
} else {
Bitmap cached = bitmapCache.getBitmapFromMemCache(userId);
if (cached != null) {
Bitmap bitmap = cached.copy(cached.getConfig(), false);
return getCircleBitmap(bitmap);
}
String url = String.format("api/v4/users/%s/image", userId);
final ReactApplicationContext reactApplicationContext = new ReactApplicationContext(context);
final String token = Credentials.getCredentialsForServerSync(reactApplicationContext, serverUrl);
url = String.format("%s/api/v4/users/%s/image", serverUrl, userId);
Log.i("ReactNative", String.format("Fetch profile image %s", url));
response = Network.getSync(serverUrl, url, null);
request = new Request.Builder()
.header("Authorization", String.format("Bearer %s", token))
.url(url)
.build();
}
Response response = client.newCall(request).execute();
if (response.code() == 200) {
assert response.body() != null;
byte[] bytes = Objects.requireNonNull(response.body()).bytes();
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
if (TextUtils.isEmpty(urlOverride) && !TextUtils.isEmpty(userId)) {
bitmapCache.addBitmapToMemoryCache(userId, bitmap.copy(bitmap.getConfig(), false));
}
return getCircleBitmap(bitmap);
}

View File

@@ -12,7 +12,6 @@ import com.mattermost.networkclient.APIClientModule;
import com.mattermost.networkclient.enums.RetryTypes;
import okhttp3.HttpUrl;
import okhttp3.Response;
public class Network {
@@ -36,16 +35,6 @@ public class Network {
clientModule.post(baseUrl, endpoint, options, promise);
}
public static Response getSync(String baseUrl, String endpoint, ReadableMap options) {
createClientIfNeeded(baseUrl);
return clientModule.getSync(baseUrl, endpoint, options);
}
public static Response postSync(String baseUrl, String endpoint, ReadableMap options) {
createClientIfNeeded(baseUrl);
return clientModule.postSync(baseUrl, endpoint, options);
}
private static void createClientOptions() {
WritableMap headers = Arguments.createMap();
headers.putString("X-Requested-With", "XMLHttpRequest");

View File

@@ -7,6 +7,7 @@ import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import java.util.Objects;
@@ -16,6 +17,7 @@ import com.mattermost.helpers.DatabaseHelper;
import com.mattermost.helpers.Network;
import com.mattermost.helpers.NotificationHelper;
import com.mattermost.helpers.PushNotificationDataHelper;
import com.mattermost.helpers.ResolvePromise;
import com.mattermost.share.ShareModule;
import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
import com.wix.reactnativenotifications.core.notification.PushNotification;
@@ -54,31 +56,27 @@ public class CustomPushNotification extends PushNotification {
boolean isReactInit = mAppLifecycleFacade.isReactInitialized();
if (ackId != null && serverUrl != null) {
Bundle response = ReceiptDelivery.send(ackId, serverUrl, postId, type, isIdLoaded);
if (isIdLoaded && response != null) {
Bundle current = mNotificationProps.asBundle();
if (!current.containsKey("server_url")) {
response.putString("server_url", serverUrl);
notificationReceiptDelivery(ackId, serverUrl, postId, type, isIdLoaded, new ResolvePromise() {
@Override
public void resolve(@Nullable Object value) {
if (isIdLoaded) {
Bundle response = (Bundle) value;
if (value != null) {
response.putString("server_url", serverUrl);
Bundle current = mNotificationProps.asBundle();
current.putAll(response);
mNotificationProps = createProps(current);
}
}
}
current.putAll(response);
mNotificationProps = createProps(current);
}
@Override
public void reject(String code, String message) {
Log.e("ReactNative", code + ": " + message);
}
});
}
finishProcessingNotification(serverUrl, type, channelId, notificationId, isReactInit);
}
@Override
public void onOpened() {
if (mNotificationProps != null) {
digestNotification();
Bundle data = mNotificationProps.asBundle();
NotificationHelper.clearChannelOrThreadNotifications(mContext, data);
}
}
private void finishProcessingNotification(String serverUrl, String type, String channelId, int notificationId, Boolean isReactInit) {
switch (type) {
case CustomPushNotificationHelper.PUSH_TYPE_MESSAGE:
case CustomPushNotificationHelper.PUSH_TYPE_SESSION:
@@ -119,6 +117,16 @@ public class CustomPushNotification extends PushNotification {
}
}
@Override
public void onOpened() {
if (mNotificationProps != null) {
digestNotification();
Bundle data = mNotificationProps.asBundle();
NotificationHelper.clearChannelOrThreadNotifications(mContext, data);
}
}
private void buildNotification(Integer notificationId, boolean createSummary) {
final PendingIntent pendingIntent = NotificationIntentAdapter.createPendingNotificationIntent(mContext, mNotificationProps);
final Notification notification = buildNotification(pendingIntent);
@@ -140,6 +148,10 @@ public class CustomPushNotification extends PushNotification {
return CustomPushNotificationHelper.createNotificationBuilder(mContext, intent, bundle, true);
}
private void notificationReceiptDelivery(String ackId, String serverUrl, String postId, String type, boolean isIdLoaded, ResolvePromise promise) {
ReceiptDelivery.send(mContext, ackId, serverUrl, postId, type, isIdLoaded, promise);
}
private void notifyReceivedToJS() {
mJsIOHelper.sendEventToJS(NOTIFICATION_RECEIVED_EVENT_NAME, mNotificationProps.asBundle(), mAppLifecycleFacade.getRunningReactContext());
}

View File

@@ -8,16 +8,28 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import androidx.core.app.Person;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import android.util.Log;
import java.io.IOException;
import java.util.Objects;
import okhttp3.Call;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.json.JSONObject;
import org.json.JSONException;
import com.mattermost.helpers.*;
import com.facebook.react.bridge.ReactApplicationContext;
import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
import com.wix.reactnativenotifications.core.notification.PushNotificationProps;
@@ -41,7 +53,12 @@ public class NotificationReplyBroadcastReceiver extends BroadcastReceiver {
final int notificationId = intent.getIntExtra(CustomPushNotificationHelper.NOTIFICATION_ID, -1);
final String serverUrl = bundle.getString("server_url");
if (serverUrl != null) {
replyToMessage(serverUrl, notificationId, message);
final ReactApplicationContext reactApplicationContext = new ReactApplicationContext(context);
final String token = Credentials.getCredentialsForServerSync(reactApplicationContext, serverUrl);
if (token != null) {
replyToMessage(serverUrl, token, notificationId, message);
}
} else {
onReplyFailed(notificationId);
}
@@ -50,7 +67,7 @@ public class NotificationReplyBroadcastReceiver extends BroadcastReceiver {
}
}
protected void replyToMessage(final String serverUrl, final int notificationId, final CharSequence message) {
protected void replyToMessage(final String serverUrl, final String token, final int notificationId, final CharSequence message) {
final String channelId = bundle.getString("channel_id");
final String postId = bundle.getString("post_id");
String rootId = bundle.getString("root_id");
@@ -58,53 +75,63 @@ public class NotificationReplyBroadcastReceiver extends BroadcastReceiver {
rootId = postId;
}
if (serverUrl == null) {
if (token == null || serverUrl == null) {
onReplyFailed(notificationId);
return;
}
WritableMap headers = Arguments.createMap();
headers.putString("Content-Type", "application/json");
WritableMap body = Arguments.createMap();
body.putString("channel_id", channelId);
body.putString("message", message.toString());
body.putString("root_id", rootId);
WritableMap options = Arguments.createMap();
options.putMap("headers", headers);
options.putMap("body", body);
final OkHttpClient client = new OkHttpClient();
final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
String json = buildReplyPost(channelId, rootId, message.toString());
RequestBody body = RequestBody.create(json, JSON);
String postsEndpoint = "/api/v4/posts?set_online=false";
Network.post(serverUrl, postsEndpoint, options, new ResolvePromise() {
String url = String.format("%s%s", serverUrl.replaceAll("/$", ""), postsEndpoint);
Log.i("ReactNative", String.format("Reply URL=%s", url));
Request request = new Request.Builder()
.header("Authorization", String.format("Bearer %s", token))
.header("Content-Type", "application/json")
.url(url)
.post(body)
.build();
client.newCall(request).enqueue(new okhttp3.Callback() {
@Override
public void resolve(@Nullable Object value) {
if (value != null) {
public void onFailure(@NonNull Call call, @NonNull IOException e) {
Log.i("ReactNative", String.format("Reply FAILED exception %s", e.getMessage()));
onReplyFailed(notificationId);
}
@Override
public void onResponse(@NonNull Call call, @NonNull final Response response) throws IOException {
if (response.isSuccessful()) {
onReplySuccess(notificationId, message);
Log.i("ReactNative", "Reply SUCCESS");
} else {
Log.i("ReactNative", "Reply FAILED resolved without value");
Log.i("ReactNative",
String.format("Reply FAILED status %s BODY %s",
response.code(),
Objects.requireNonNull(response.body()).string()
)
);
onReplyFailed(notificationId);
}
}
@Override
public void reject(Throwable reason) {
Log.i("ReactNative", String.format("Reply FAILED exception %s", reason.getMessage()));
onReplyFailed(notificationId);
}
@Override
public void reject(String code, String message) {
Log.i("ReactNative",
String.format("Reply FAILED status %s BODY %s", code, message)
);
onReplyFailed(notificationId);
}
});
}
protected String buildReplyPost(String channelId, String rootId, String message) {
try {
JSONObject json = new JSONObject();
json.put("channel_id", channelId);
json.put("message", message);
json.put("root_id", rootId);
return json.toString();
} catch(JSONException e) {
return "{}";
}
}
protected void onReplyFailed(int notificationId) {
recreateNotification(notificationId, "Message failed to send.");
}

View File

@@ -1,60 +1,135 @@
package com.mattermost.rnbeta;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import java.lang.System;
import java.util.Objects;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.HttpUrl;
import org.json.JSONObject;
import org.json.JSONException;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.WritableMap;
import com.mattermost.helpers.*;
import okhttp3.Response;
public class ReceiptDelivery {
private static final String[] ackKeys = new String[]{"post_id", "root_id", "category", "message", "team_id", "channel_id", "channel_name", "type", "sender_id", "sender_name", "version"};
private static final int[] FIBONACCI_BACKOFF = new int[] { 0, 1, 2, 3, 5, 8 };
public static Bundle send(final String ackId, final String serverUrl, final String postId, final String type, final boolean isIdLoaded) {
public static void send(Context context, final String ackId, final String serverUrl, final String postId, final String type, final boolean isIdLoaded, ResolvePromise promise) {
final ReactApplicationContext reactApplicationContext = new ReactApplicationContext(context);
final String token = Credentials.getCredentialsForServerSync(reactApplicationContext, serverUrl);
Log.i("ReactNative", String.format("Send receipt delivery ACK=%s TYPE=%s to URL=%s with ID-LOADED=%s", ackId, type, serverUrl, isIdLoaded));
WritableMap options = Arguments.createMap();
WritableMap headers = Arguments.createMap();
WritableMap body = Arguments.createMap();
headers.putString("Content-Type", "application/json");
options.putMap("headers", headers);
body.putString("id", ackId);
body.putDouble("received_at", System.currentTimeMillis());
body.putString("platform", "android");
body.putString("type", type);
body.putString("post_id", postId);
body.putBoolean("is_id_loaded", isIdLoaded);
options.putMap("body", body);
execute(serverUrl, postId, token, ackId, type, isIdLoaded, promise);
}
try (Response response = Network.postSync(serverUrl, "api/v4/notifications/ack", options)) {
String responseBody = Objects.requireNonNull(response.body()).string();
JSONObject jsonResponse = new JSONObject(responseBody);
return parseAckResponse(jsonResponse);
} catch (Exception e) {
e.printStackTrace();
return null;
protected static void execute(String serverUrl, String postId, String token, String ackId, String type, boolean isIdLoaded, ResolvePromise promise) {
if (token == null) {
promise.reject("Receipt delivery failure", "Invalid token");
return;
}
if (serverUrl == null) {
promise.reject("Receipt delivery failure", "Invalid server URL");
}
JSONObject json;
long receivedAt = System.currentTimeMillis();
try {
json = new JSONObject();
json.put("id", ackId);
json.put("received_at", receivedAt);
json.put("platform", "android");
json.put("type", type);
json.put("post_id", postId);
json.put("is_id_loaded", isIdLoaded);
} catch (JSONException e) {
Log.e("ReactNative", "Receipt delivery failed to build json payload");
promise.reject("Receipt delivery failure", e.toString());
return;
}
final HttpUrl url;
if (serverUrl != null) {
url = HttpUrl.parse(
String.format("%s/api/v4/notifications/ack", serverUrl.replaceAll("/$", "")));
if (url != null) {
final OkHttpClient client = new OkHttpClient();
final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
RequestBody body = RequestBody.create(json.toString(), JSON);
Request request = new Request.Builder()
.header("Authorization", String.format("Bearer %s", token))
.header("Content-Type", "application/json")
.url(url)
.post(body)
.build();
makeServerRequest(client, request, isIdLoaded, 0, promise);
}
}
}
public static Bundle parseAckResponse(JSONObject jsonResponse) {
private static void makeServerRequest(OkHttpClient client, Request request, Boolean isIdLoaded, int reRequestCount, ResolvePromise promise) {
try {
Response response = client.newCall(request).execute();
String responseBody = Objects.requireNonNull(response.body()).string();
if (response.code() != 200) {
switch (response.code()) {
case 302:
promise.reject("Receipt delivery failure", "StatusFound");
return;
case 400:
promise.reject("Receipt delivery failure", "StatusBadRequest");
return;
case 401:
promise.reject("Receipt delivery failure", "Unauthorized");
case 403:
promise.reject("Receipt delivery failure", "Forbidden");
return;
case 500:
promise.reject("Receipt delivery failure", "StatusInternalServerError");
return;
case 501:
promise.reject("Receipt delivery failure", "StatusNotImplemented");
return;
}
throw new Exception(responseBody);
}
JSONObject jsonResponse = new JSONObject(responseBody);
Bundle bundle = new Bundle();
for (String key : ackKeys) {
String[] keys = new String[]{"post_id", "root_id", "category", "message", "team_id", "channel_id", "channel_name", "type", "sender_id", "sender_name", "version"};
for (String key : keys) {
if (jsonResponse.has(key)) {
bundle.putString(key, jsonResponse.getString(key));
}
}
return bundle;
promise.resolve(bundle);
} catch (Exception e) {
e.printStackTrace();
return null;
Log.e("ReactNative", "Receipt delivery failed to send");
if (isIdLoaded) {
try {
reRequestCount++;
if (reRequestCount < FIBONACCI_BACKOFF.length) {
Log.i("ReactNative", "Retry attempt " + reRequestCount + " with backoff delay: " + FIBONACCI_BACKOFF[reRequestCount] + " seconds");
Thread.sleep(FIBONACCI_BACKOFF[reRequestCount] * 1000);
makeServerRequest(client, request, true, reRequestCount, promise);
}
} catch(InterruptedException ie) {
ie.printStackTrace();
}
}
promise.reject("Receipt delivery failure", e.toString());
}
}
}

View File

@@ -9,6 +9,7 @@ import {queryMyTeams} from '@queries/servers/team';
import {isDMorGM} from '@utils/channel';
import {logError} from '@utils/log';
import type {Model} from '@nozbe/watermelondb';
import type ChannelModel from '@typings/database/models/servers/channel';
export const deleteCategory = async (serverUrl: string, categoryId: string) => {
@@ -39,7 +40,7 @@ export async function storeCategories(serverUrl: string, categories: CategoryWit
}
if (models.length > 0) {
await operator.batchRecords(models, 'storeCategories');
await operator.batchRecords(models);
}
return {models};
@@ -79,6 +80,7 @@ export async function addChannelToDefaultCategory(serverUrl: string, channel: Ch
return {error: 'no current user id'};
}
const models: Model[] = [];
const categoriesWithChannels: CategoryWithChannels[] = [];
if (isDMorGM(channel)) {
@@ -98,12 +100,13 @@ export async function addChannelToDefaultCategory(serverUrl: string, channel: Ch
cwc.channel_ids.unshift(channel.id);
categoriesWithChannels.push(cwc);
}
const ccModels = await prepareCategoryChannels(operator, categoriesWithChannels);
models.push(...ccModels);
}
const models = await prepareCategoryChannels(operator, categoriesWithChannels);
if (models.length && !prepareRecordsOnly) {
await operator.batchRecords(models, 'addChannelToDefaultCategory');
await operator.batchRecords(models);
}
return {models};

View File

@@ -13,7 +13,7 @@ import {
prepareDeleteChannel, prepareMyChannelsForTeam, queryAllMyChannel,
getMyChannel, getChannelById, queryUsersOnChannel, queryUserChannelsByTypes,
} from '@queries/servers/channel';
import {queryDisplayNamePreferences} from '@queries/servers/preference';
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
import {prepareCommonSystemValues, PrepareCommonSystemValuesArgs, getCommonSystemValues, getCurrentTeamId, setCurrentChannelId, getCurrentUserId, getConfig, getLicense} from '@queries/servers/system';
import {addChannelToTeamHistory, addTeamToTeamHistory, getTeamById, removeChannelFromTeamHistory} from '@queries/servers/team';
import {getCurrentUser, queryUsersById} from '@queries/servers/user';
@@ -82,7 +82,7 @@ export async function switchToChannel(serverUrl: string, channelId: string, team
}
if (models.length && !prepareRecordsOnly) {
await operator.batchRecords(models, 'switchToChannel');
await operator.batchRecords(models);
}
if (isTabletDevice) {
@@ -124,7 +124,7 @@ export async function removeCurrentUserFromChannel(serverUrl: string, channelId:
await removeChannelFromTeamHistory(operator, teamId, channel.id, false);
if (models.length && !prepareRecordsOnly) {
await operator.batchRecords(models, 'removeCurrentUserFromChannel');
await operator.batchRecords(models);
}
}
return {models};
@@ -145,7 +145,7 @@ export async function setChannelDeleteAt(serverUrl: string, channelId: string, d
const model = channel.prepareUpdate((c) => {
c.deleteAt = deleteAt;
});
await operator.batchRecords([model], 'setChannelDeleteAt');
await operator.batchRecords([model]);
} catch (error) {
logError('FAILED TO BATCH CHANGES FOR CHANNEL DELETE AT', error);
}
@@ -179,7 +179,7 @@ export async function markChannelAsViewed(serverUrl: string, channelId: string,
});
PushNotifications.removeChannelNotifications(serverUrl, channelId);
if (!prepareRecordsOnly) {
await operator.batchRecords([member], 'markChannelAsViewed');
await operator.batchRecords([member]);
}
return {member};
@@ -206,7 +206,7 @@ export async function markChannelAsUnread(serverUrl: string, channelId: string,
m.isUnread = true;
});
if (!prepareRecordsOnly) {
await operator.batchRecords([member], 'markChannelAsUnread');
await operator.batchRecords([member]);
}
return {member};
@@ -226,7 +226,7 @@ export async function resetMessageCount(serverUrl: string, channelId: string) {
member.prepareUpdate((m) => {
m.messageCount = 0;
});
await operator.batchRecords([member], 'resetMessageCount');
await operator.batchRecords([member]);
return member;
} catch (error) {
@@ -254,7 +254,7 @@ export async function storeMyChannelsForTeam(serverUrl: string, teamId: string,
}
if (flattenedModels.length) {
await operator.batchRecords(flattenedModels, 'storeMyChannelsForTeam');
await operator.batchRecords(flattenedModels);
}
return {models: flattenedModels};
@@ -273,7 +273,7 @@ export async function updateMyChannelFromWebsocket(serverUrl: string, channelMem
m.roles = channelMember.roles;
});
if (!prepareRecordsOnly) {
operator.batchRecords([member], 'updateMyChannelFromWebsocket');
operator.batchRecords([member]);
}
}
return {model: member};
@@ -293,7 +293,7 @@ export async function updateChannelInfoFromChannel(serverUrl: string, channel: C
}],
prepareRecordsOnly: true});
if (!prepareRecordsOnly) {
operator.batchRecords(newInfo, 'updateChannelInfoFromChannel');
operator.batchRecords(newInfo);
}
return {model: newInfo};
} catch (error) {
@@ -317,7 +317,7 @@ export async function updateLastPostAt(serverUrl: string, channelId: string, las
});
if (!prepareRecordsOnly) {
await operator.batchRecords([myChannel], 'updateLastPostAt');
await operator.batchRecords([myChannel]);
}
return {member: myChannel};
@@ -345,7 +345,7 @@ export async function updateMyChannelLastFetchedAt(serverUrl: string, channelId:
});
if (!prepareRecordsOnly) {
await operator.batchRecords([myChannel], 'updateMyChannelLastFetchedAt');
await operator.batchRecords([myChannel]);
}
return {member: myChannel};
@@ -369,7 +369,7 @@ export async function updateChannelsDisplayName(serverUrl: string, channels: Cha
const license = await getLicense(database);
const config = await getConfig(database);
const preferences = await queryDisplayNamePreferences(database, Preferences.NAME_NAME_FORMAT).fetch();
const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch();
const displaySettings = getTeammateNameDisplaySetting(preferences, config.LockTeammateNameDisplay, config.TeammateNameDisplay, license);
const models: Model[] = [];
for await (const channel of channels) {
@@ -403,7 +403,7 @@ export async function updateChannelsDisplayName(serverUrl: string, channels: Cha
}
if (models.length && !prepareRecordsOnly) {
await operator.batchRecords(models, 'updateChannelsDisplayName');
await operator.batchRecords(models);
}
return {models};

View File

@@ -26,7 +26,7 @@ export async function updateDraftFile(serverUrl: string, channelId: string, root
});
if (!prepareRecordsOnly) {
await operator.batchRecords([draft], 'updateDraftFile');
await operator.batchRecords([draft]);
}
return {draft};
@@ -58,7 +58,7 @@ export async function removeDraftFile(serverUrl: string, channelId: string, root
}
if (!prepareRecordsOnly) {
await operator.batchRecords([draft], 'removeDraftFile');
await operator.batchRecords([draft]);
}
return {draft};
@@ -99,7 +99,7 @@ export async function updateDraftMessage(serverUrl: string, channelId: string, r
}
if (!prepareRecordsOnly) {
await operator.batchRecords([draft], 'updateDraftMessage');
await operator.batchRecords([draft]);
}
return {draft};
@@ -129,7 +129,7 @@ export async function addFilesToDraft(serverUrl: string, channelId: string, root
});
if (!prepareRecordsOnly) {
await operator.batchRecords([draft], 'addFilesToDraft');
await operator.batchRecords([draft]);
}
return {draft};

View File

@@ -127,14 +127,14 @@ export async function removePost(serverUrl: string, post: PostModel | Post) {
}
if (removeModels.length) {
await operator.batchRecords(removeModels, 'removePost (combined user activity)');
await operator.batchRecords(removeModels);
}
} else {
const postModel = await getPostById(database, post.id);
if (postModel) {
const preparedPost = await prepareDeletePost(postModel);
if (preparedPost.length) {
await operator.batchRecords(preparedPost, 'removePost');
await operator.batchRecords(preparedPost);
}
}
}
@@ -162,7 +162,7 @@ export async function markPostAsDeleted(serverUrl: string, post: Post, prepareRe
});
if (!prepareRecordsOnly) {
await operator.batchRecords([dbPost], 'markPostAsDeleted');
await operator.batchRecords([dbPost]);
}
return {model};
} catch (error) {
@@ -229,7 +229,7 @@ export async function storePostsForChannel(
}
if (models.length && !prepareRecordsOnly) {
await operator.batchRecords(models, 'storePostsForChannel');
await operator.batchRecords(models);
}
return {models};

View File

@@ -22,7 +22,7 @@ export async function removeUserFromTeam(serverUrl: string, teamId: string) {
models.push(...system);
}
if (models.length) {
await operator.batchRecords(models, 'removeUserFromTeam');
await operator.batchRecords(models);
}
}
@@ -61,7 +61,7 @@ export async function addSearchToTeamSearchHistory(serverUrl: string, teamId: st
}
}
await operator.batchRecords(models, 'addSearchToTeamHistory');
await operator.batchRecords(models);
return {searchModel};
} catch (error) {
logError('Failed addSearchToTeamSearchHistory', error);

View File

@@ -40,7 +40,7 @@ export const switchToGlobalThreads = async (serverUrl: string, teamId?: string,
models.push(...history);
if (!prepareRecordsOnly) {
await operator.batchRecords(models, 'switchToGlobalThreads');
await operator.batchRecords(models);
}
const isTabletDevice = await isTablet();
@@ -84,7 +84,7 @@ export const switchToThread = async (serverUrl: string, rootId: string, isFromNo
currentChannelId: channel.id,
});
if (models.length) {
await operator.batchRecords(models, 'switchToThread');
await operator.batchRecords(models);
}
} else {
const modelPromises: Array<Promise<Model[]>> = [];
@@ -97,7 +97,7 @@ export const switchToThread = async (serverUrl: string, rootId: string, isFromNo
modelPromises.push(prepareCommonSystemValues(operator, commonValues));
const models = (await Promise.all(modelPromises)).flat();
if (models.length) {
await operator.batchRecords(models, 'switchToThread');
await operator.batchRecords(models);
}
}
@@ -201,7 +201,7 @@ export async function createThreadFromNewPost(serverUrl: string, post: Post, pre
}
if (!prepareRecordsOnly) {
await operator.batchRecords(models, 'createThreadFromNewPost');
await operator.batchRecords(models);
}
return {models};
@@ -257,7 +257,7 @@ export async function processReceivedThreads(serverUrl: string, threads: Thread[
}
if (!prepareRecordsOnly) {
await operator.batchRecords(models, 'processReceivedThreads');
await operator.batchRecords(models);
}
return {models};
} catch (error) {
@@ -277,7 +277,7 @@ export async function markTeamThreadsAsRead(serverUrl: string, teamId: string, p
record.viewedAt = Date.now();
}));
if (!prepareRecordsOnly) {
await operator.batchRecords(models, 'markTeamThreadsAsRead');
await operator.batchRecords(models);
}
return {models};
} catch (error) {
@@ -300,7 +300,7 @@ export async function markThreadAsViewed(serverUrl: string, threadId: string, pr
});
if (!prepareRecordsOnly) {
await operator.batchRecords([thread], 'markThreadAsViewed');
await operator.batchRecords([thread]);
}
return {model: thread};
@@ -327,7 +327,7 @@ export async function updateThread(serverUrl: string, threadId: string, updatedT
record.unreadReplies = updatedThread.unread_replies ?? record.unreadReplies;
});
if (!prepareRecordsOnly) {
await operator.batchRecords([model], 'updateThread');
await operator.batchRecords([model]);
}
return {model};
} catch (error) {
@@ -341,7 +341,7 @@ export async function updateTeamThreadsSync(serverUrl: string, data: TeamThreads
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const models = await operator.handleTeamThreadsSync({data: [data], prepareRecordsOnly});
if (!prepareRecordsOnly) {
await operator.batchRecords(models, 'updateTeamThreadsSync');
await operator.batchRecords(models);
}
return {models};
} catch (error) {

View File

@@ -22,7 +22,7 @@ export async function setCurrentUserStatusOffline(serverUrl: string) {
}
user.prepareStatus(General.OFFLINE);
await operator.batchRecords([user], 'setCurrentUserStatusOffline');
await operator.batchRecords([user]);
return null;
} catch (error) {
logError('Failed setCurrentUserStatusOffline', error);
@@ -54,7 +54,7 @@ export async function updateLocalCustomStatus(serverUrl: string, user: UserModel
}
}
await operator.batchRecords(models, 'updateLocalCustomStatus');
await operator.batchRecords(models);
return {};
} catch (error) {
@@ -97,37 +97,27 @@ export const updateRecentCustomStatuses = async (serverUrl: string, customStatus
export const updateLocalUser = async (
serverUrl: string,
userDetails: Partial<UserProfile> & { status?: string},
userId?: string,
) => {
try {
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
let user: UserModel | undefined;
if (userId) {
user = await getUserById(database, userId);
} else {
user = await getCurrentUser(database);
}
const user = await getCurrentUser(database);
if (user) {
const u = user;
await database.write(async () => {
await u.update((userRecord: UserModel) => {
userRecord.authService = userDetails.auth_service ?? u.authService;
userRecord.email = userDetails.email ?? u.email;
userRecord.firstName = userDetails.first_name ?? u.firstName;
userRecord.lastName = userDetails.last_name ?? u.lastName;
userRecord.lastPictureUpdate = userDetails.last_picture_update ?? u.lastPictureUpdate;
userRecord.locale = userDetails.locale ?? u.locale;
userRecord.nickname = userDetails.nickname ?? u.nickname;
userRecord.notifyProps = userDetails.notify_props ?? u.notifyProps;
userRecord.position = userDetails?.position ?? u.position;
userRecord.props = userDetails.props ?? u.props;
userRecord.roles = userDetails.roles ?? u.roles;
userRecord.status = userDetails?.status ?? u.status;
userRecord.timezone = userDetails.timezone ?? u.timezone;
userRecord.username = userDetails.username ?? u.username;
await user.update((userRecord: UserModel) => {
userRecord.authService = userDetails.auth_service ?? user.authService;
userRecord.email = userDetails.email ?? user.email;
userRecord.firstName = userDetails.first_name ?? user.firstName;
userRecord.lastName = userDetails.last_name ?? user.lastName;
userRecord.lastPictureUpdate = userDetails.last_picture_update ?? user.lastPictureUpdate;
userRecord.locale = userDetails.locale ?? user.locale;
userRecord.nickname = userDetails.nickname ?? user.nickname;
userRecord.notifyProps = userDetails.notify_props ?? user.notifyProps;
userRecord.position = userDetails?.position ?? user.position;
userRecord.props = userDetails.props ?? user.props;
userRecord.roles = userDetails.roles ?? user.roles;
userRecord.status = userDetails?.status ?? user.status;
userRecord.timezone = userDetails.timezone ?? user.timezone;
userRecord.username = userDetails.username ?? user.username;
});
});
}

View File

@@ -7,7 +7,6 @@ import {DeviceEventEmitter} from 'react-native';
import {addChannelToDefaultCategory, storeCategories} from '@actions/local/category';
import {removeCurrentUserFromChannel, setChannelDeleteAt, storeMyChannelsForTeam, switchToChannel} from '@actions/local/channel';
import {switchToGlobalThreads} from '@actions/local/thread';
import {updateLocalUser} from '@actions/local/user';
import {loadCallForChannel} from '@calls/actions/calls';
import {DeepLink, Events, General, Preferences, Screens} from '@constants';
import DatabaseManager from '@database/manager';
@@ -16,8 +15,8 @@ import {getTeammateNameDisplaySetting} from '@helpers/api/preference';
import AppsManager from '@managers/apps_manager';
import NetworkManager from '@managers/network_manager';
import {getActiveServer} from '@queries/app/servers';
import {prepareMyChannelsForTeam, getChannelById, getChannelByName, getMyChannel, getChannelInfo, queryMyChannelSettingsByIds, getMembersCountByChannelsId, deleteChannelMembership, queryChannelsById} from '@queries/servers/channel';
import {queryDisplayNamePreferences} from '@queries/servers/preference';
import {prepareMyChannelsForTeam, getChannelById, getChannelByName, getMyChannel, getChannelInfo, queryMyChannelSettingsByIds, getMembersCountByChannelsId} from '@queries/servers/channel';
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
import {getCommonSystemValues, getConfig, getCurrentChannelId, getCurrentTeamId, getCurrentUserId, getLicense, setCurrentChannelId, setCurrentTeamAndChannelId} from '@queries/servers/system';
import {getNthLastChannelFromTeam, getMyTeamById, getTeamByName, queryMyTeams, removeChannelFromTeamHistory} from '@queries/servers/team';
import {getCurrentUser} from '@queries/servers/user';
@@ -36,10 +35,9 @@ import {setDirectChannelVisible} from './preference';
import {fetchRolesIfNeeded} from './role';
import {forceLogoutIfNecessary} from './session';
import {addCurrentUserToTeam, fetchTeamByName, removeCurrentUserFromTeam} from './team';
import {fetchProfilesInChannel, fetchProfilesInGroupChannels, fetchProfilesPerChannels, fetchUsersByIds, updateUsersNoLongerVisible} from './user';
import {fetchProfilesInGroupChannels, fetchProfilesPerChannels, fetchUsersByIds, updateUsersNoLongerVisible} from './user';
import type {Client} from '@client/rest';
import type ClientError from '@client/rest/error';
import type {Model} from '@nozbe/watermelondb';
import type ChannelModel from '@typings/database/models/servers/channel';
import type {IntlShape} from 'react-intl';
@@ -51,122 +49,37 @@ export type MyChannelsRequest = {
error?: unknown;
}
export type ChannelMembersRequest = {
members?: ChannelMembership[];
error?: unknown;
}
export async function removeMemberFromChannel(serverUrl: string, channelId: string, userId: string) {
try {
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const client = NetworkManager.getClient(serverUrl);
await client.removeFromChannel(userId, channelId);
await deleteChannelMembership(operator, userId, channelId);
return {error: undefined};
} catch (error) {
logError('removeMemberFromChannel', error);
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
return {error};
}
}
export async function fetchChannelMembersByIds(serverUrl: string, channelId: string, userIds: string[], fetchOnly = false): Promise<ChannelMembersRequest> {
try {
const client = NetworkManager.getClient(serverUrl);
const members = await client.getChannelMembersByIds(channelId, userIds);
if (!fetchOnly) {
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
if (operator && members.length) {
const memberships = members.map((u) => ({
channel_id: channelId,
user_id: u.user_id,
scheme_admin: u.scheme_admin,
}));
await operator.handleChannelMembership({
channelMemberships: memberships,
prepareRecordsOnly: false,
});
}
}
return {members};
} catch (error) {
logError('fetchChannelMembersByIds', error);
forceLogoutIfNecessary(serverUrl, error as ClientError);
return {error};
}
}
export async function updateChannelMemberSchemeRoles(serverUrl: string, channelId: string, userId: string, isSchemeUser: boolean, isSchemeAdmin: boolean, fetchOnly = false) {
try {
const client = NetworkManager.getClient(serverUrl);
await client.updateChannelMemberSchemeRoles(channelId, userId, isSchemeUser, isSchemeAdmin);
if (!fetchOnly) {
return getMemberInChannel(serverUrl, channelId, userId);
}
return {error: undefined};
} catch (error) {
logError('updateChannelMemberSchemeRoles', error);
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
return {error};
}
}
export async function getMemberInChannel(serverUrl: string, channelId: string, userId: string, fetchOnly = false) {
try {
const client = NetworkManager.getClient(serverUrl);
const member = await client.getMemberInChannel(channelId, userId);
if (!fetchOnly) {
updateLocalUser(serverUrl, member, userId);
}
return {member, error: undefined};
} catch (error) {
logError('getMemberInChannel', error);
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
return {error};
}
}
export async function fetchChannelMemberships(serverUrl: string, channelId: string, options: GetUsersOptions, fetchOnly = false) {
const {users = []} = await fetchProfilesInChannel(serverUrl, channelId, undefined, options, fetchOnly);
const userIds = users.map((u) => u.id);
// MM-49896 https://mattermost.atlassian.net/browse/MM-49896
// We are not sure the getChannelMembers API returns the same members
// from getProfilesInChannel. This guarantees a 1:1 match of the
// user IDs
const {members = []} = await fetchChannelMembersByIds(serverUrl, channelId, userIds, true);
return {users, members};
}
export async function addMembersToChannel(serverUrl: string, channelId: string, userIds: string[], postRootId = '', fetchOnly = false) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
let client: Client;
try {
client = NetworkManager.getClient(serverUrl);
} catch (error) {
return {error};
}
try {
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const client = NetworkManager.getClient(serverUrl);
const promises = userIds.map((id) => client.addToChannel(id, channelId, postRootId));
const channelMemberships: ChannelMembership[] = await Promise.all(promises);
const {users} = await fetchUsersByIds(serverUrl, userIds, true);
if (!fetchOnly) {
const modelPromises: Array<Promise<Model[]>> = [];
if (users?.length) {
modelPromises.push(operator.handleUsers({
users,
prepareRecordsOnly: true,
}));
}
modelPromises.push(operator.handleUsers({
users,
prepareRecordsOnly: true,
}));
modelPromises.push(operator.handleChannelMembership({
channelMemberships,
prepareRecordsOnly: true,
}));
const models = await Promise.all(modelPromises);
await operator.batchRecords(models.flat(), 'addMembersToChannel');
await operator.batchRecords(models.flat());
}
return {channelMemberships};
} catch (error) {
@@ -243,7 +156,7 @@ export async function createChannel(serverUrl: string, displayName: string, purp
models.push(...categoriesModels.models);
}
if (models.length) {
await operator.batchRecords(models, 'createChannel');
await operator.batchRecords(models);
}
fetchChannelStats(serverUrl, channelData.id, false);
EphemeralStore.creatingChannel = false;
@@ -288,7 +201,7 @@ export async function patchChannel(serverUrl: string, channelPatch: Partial<Chan
models.push(channel);
}
if (models?.length) {
await operator.batchRecords(models.flat(), 'patchChannel');
await operator.batchRecords(models.flat());
}
return {channel: channelData};
} catch (error) {
@@ -335,7 +248,7 @@ export async function leaveChannel(serverUrl: string, channelId: string) {
models.push(...removeUserModels);
}
await operator.batchRecords(models, 'leaveChannel');
await operator.batchRecords(models);
if (isTabletDevice) {
switchToLastChannel(serverUrl);
@@ -385,7 +298,7 @@ export async function fetchChannelCreator(serverUrl: string, channelId: string,
}));
const models = await Promise.all(modelPromises);
await operator.batchRecords(models.flat(), 'fetchChannelCreator');
await operator.batchRecords(models.flat());
}
return {user};
@@ -476,7 +389,7 @@ export async function fetchMyChannelsForTeam(serverUrl: string, teamId: string,
const {models: catModels} = await storeCategories(serverUrl, categories, true, true); // Re-sync
const models = (chModels || []).concat(catModels || []);
if (models.length) {
await operator.batchRecords(models, 'fetchMyChannelsForTeam');
await operator.batchRecords(models);
}
setTeamLoading(serverUrl, false);
}
@@ -525,37 +438,38 @@ export async function fetchMyChannel(serverUrl: string, teamId: string, channelI
}
export async function fetchMissingDirectChannelsInfo(serverUrl: string, directChannels: Channel[], locale?: string, teammateDisplayNameSetting?: string, currentUserId?: string, fetchOnly = false) {
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const displayNameByChannel: Record<string, string> = {};
const users: UserProfile[] = [];
const updatedChannels = new Set<Channel>();
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const dms: Channel[] = [];
const dmIds: string[] = [];
const dmWithoutDisplayName = new Set<string>();
const gms: Channel[] = [];
const channelIds = new Set(directChannels.map((c) => c.id));
const storedChannels = await queryChannelsById(database, Array.from(channelIds)).fetch();
const storedChannelsMap = new Map(storedChannels.map((c) => [c.id, c]));
const {database} = operator;
const displayNameByChannel: Record<string, string> = {};
const users: UserProfile[] = [];
const updatedChannels = new Set<Channel>();
for (const c of directChannels) {
if (c.type === General.DM_CHANNEL) {
dms.push(c);
dmIds.push(c.id);
if (!c.display_name && !storedChannelsMap.get(c.id)?.displayName) {
dmWithoutDisplayName.add(c.id);
}
continue;
const dms: Channel[] = [];
const dmIds: string[] = [];
const dmWithoutDisplayName = new Set<string>();
const gms: Channel[] = [];
for (const c of directChannels) {
if (c.type === General.DM_CHANNEL) {
dms.push(c);
dmIds.push(c.id);
if (!c.display_name) {
dmWithoutDisplayName.add(c.id);
}
gms.push(c);
continue;
}
gms.push(c);
}
try {
const currentUser = await getCurrentUser(database);
// let's filter those channels that we already have the users
const membersCount = await getMembersCountByChannelsId(database, dmIds);
const profileChannelsToFetch = dmIds.filter((id) => membersCount[id] <= 1 && dmWithoutDisplayName.has(id));
const profileChannelsToFetch = dmIds.filter((id) => membersCount[id] <= 1 || dmWithoutDisplayName.has(id));
const results = await Promise.all([
profileChannelsToFetch.length ? fetchProfilesPerChannels(serverUrl, profileChannelsToFetch, currentUserId, false) : Promise.resolve({data: undefined}),
fetchProfilesInGroupChannels(serverUrl, gms.map((c) => c.id), false),
@@ -601,7 +515,7 @@ export async function fetchMissingDirectChannelsInfo(serverUrl: string, directCh
}
const models = await Promise.all(modelPromises);
await operator.batchRecords(models.flat(), 'fetchMissingDirectChannelInfo');
await operator.batchRecords(models.flat());
}
return {directChannels: updatedChannelsArray, users};
@@ -616,7 +530,7 @@ export async function fetchDirectChannelsInfo(serverUrl: string, directChannels:
return {error: `${serverUrl} database not found`};
}
const preferences = await queryDisplayNamePreferences(database).fetch();
const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS).fetch();
const config = await getConfig(database);
const license = await getLicense(database);
const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences, config?.LockTeammateNameDisplay, config?.TeammateNameDisplay, license);
@@ -679,7 +593,7 @@ export async function joinChannel(serverUrl: string, teamId: string, channelId?:
}
if (flattenedModels?.length > 0) {
try {
await operator.batchRecords(flattenedModels, 'joinChannel');
await operator.batchRecords(flattenedModels);
} catch {
logError('FAILED TO BATCH CHANNELS');
}
@@ -862,7 +776,7 @@ export async function createDirectChannel(serverUrl: string, userId: string, dis
if (displayName) {
created.display_name = displayName;
} else {
const preferences = await queryDisplayNamePreferences(database, Preferences.NAME_NAME_FORMAT).fetch();
const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch();
const license = await getLicense(database);
const config = await getConfig(database);
const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], config.LockTeammateNameDisplay, config.TeammateNameDisplay, license);
@@ -905,7 +819,7 @@ export async function createDirectChannel(serverUrl: string, userId: string, dis
models.push(...userModels);
}
await operator.batchRecords(models, 'createDirectChannel');
await operator.batchRecords(models);
EphemeralStore.creatingDMorGMTeammates = [];
fetchRolesIfNeeded(serverUrl, member.roles.split(' '));
return {data: created};
@@ -1004,7 +918,7 @@ export async function createGroupChannel(serverUrl: string, userIds: string[]) {
return {data: created};
}
const preferences = await queryDisplayNamePreferences(database, Preferences.NAME_NAME_FORMAT).fetch();
const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch();
const license = await getLicense(database);
const config = await getConfig(database);
const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], config.LockTeammateNameDisplay, config.TeammateNameDisplay, license);
@@ -1039,7 +953,7 @@ export async function createGroupChannel(serverUrl: string, userIds: string[]) {
}
models.push(...userModels);
operator.batchRecords(models, 'createGroupChannel');
operator.batchRecords(models);
}
}
EphemeralStore.creatingDMorGMTeammates = [];
@@ -1240,7 +1154,7 @@ export async function searchAllChannels(serverUrl: string, term: string, archive
}
}
export const updateChannelNotifyProps = async (serverUrl: string, channelId: string, props: Partial<ChannelNotifyProps>) => {
export const updateChannelNotifyProps = async (serverUrl: string, channelId: string, props: Partial<ChannelNotifyProps>, updateLocally = false) => {
let client: Client;
try {
client = NetworkManager.getClient(serverUrl);
@@ -1258,7 +1172,20 @@ export const updateChannelNotifyProps = async (serverUrl: string, channelId: str
const notifyProps = {...props, channel_id: channelId, user_id: userId} as ChannelNotifyProps & {channel_id: string; user_id: string};
await client.updateChannelNotifyProps(notifyProps);
if (updateLocally) {
const channelSettings = await queryMyChannelSettingsByIds(database, [channelId]).fetch();
const myChannelSetting = channelSettings?.[0];
if (myChannelSetting) {
const updatedProps: Partial<ChannelNotifyProps> = {...myChannelSetting.notifyProps, ...notifyProps};
await database.write(async () => {
await myChannelSetting.update((c) => {
c.notifyProps = updatedProps;
});
});
}
}
return {
notifyProps,
};
@@ -1277,6 +1204,10 @@ export const toggleMuteChannel = async (serverUrl: string, channelId: string, sh
try {
const channelSettings = await queryMyChannelSettingsByIds(database, [channelId]).fetch();
const myChannelSetting = channelSettings?.[0];
if (!myChannelSetting) {
return {error: 'Channel setting not found'};
}
const mark_unread = myChannelSetting.notifyProps?.mark_unread === 'mention' ? 'all' : 'mention';
const notifyProps: Partial<ChannelNotifyProps> = {...myChannelSetting.notifyProps, mark_unread};
@@ -1384,7 +1315,7 @@ export const convertChannelToPrivate = async (serverUrl: string, channelId: stri
channel.prepareUpdate((c) => {
c.type = General.PRIVATE_CHANNEL;
});
await operator.batchRecords([channel], 'convertChannelToPrivate');
await operator.batchRecords([channel]);
}
return {error: undefined};
} catch (error) {

View File

@@ -33,7 +33,7 @@ export async function appEntry(serverUrl: string, since = 0, isUpgrade = false)
// clear lastUnreadChannelId
const removeLastUnreadChannelId = await prepareCommonSystemValues(operator, {lastUnreadChannelId: ''});
if (removeLastUnreadChannelId) {
await operator.batchRecords(removeLastUnreadChannelId, 'appEntry - removeLastUnreadChannelId');
await operator.batchRecords(removeLastUnreadChannelId);
}
const {database} = operator;
@@ -58,14 +58,14 @@ export async function appEntry(serverUrl: string, since = 0, isUpgrade = false)
currentChannelId: isTabletDevice ? initialChannelId : undefined,
});
if (me?.length) {
await operator.batchRecords(me, 'appEntry - upgrade store me');
await operator.batchRecords(me);
}
}
await handleEntryAfterLoadNavigation(serverUrl, teamData.memberships || [], chData?.memberships || [], currentTeamId, currentChannelId, initialTeamId, initialChannelId);
const dt = Date.now();
await operator.batchRecords(models, 'appEntry');
await operator.batchRecords(models);
logInfo('ENTRY MODELS BATCHING TOOK', `${Date.now() - dt}ms`);
setTeamLoading(serverUrl, false);

View File

@@ -178,7 +178,7 @@ export const fetchAppEntryData = async (serverUrl: string, sinceArg: number, ini
if (!initialTeamId && teamData.teams?.length && teamData.memberships?.length) {
// If no initial team was set in the database but got teams in the response
const config = await getConfig(database);
const teamOrderPreference = getPreferenceValue<string>(prefData.preferences || [], Preferences.CATEGORIES.TEAMS_ORDER, '', '');
const teamOrderPreference = getPreferenceValue(prefData.preferences || [], Preferences.TEAMS_ORDER, '', '') as string;
const teamMembers = new Set(teamData.memberships.filter((m) => m.delete_at === 0).map((m) => m.team_id));
const myTeams = teamData.teams!.filter((t) => teamMembers.has(t.id));
const defaultTeam = selectDefaultTeam(myTeams, meData.user?.locale || DEFAULT_LOCALE, teamOrderPreference, config?.ExperimentalPrimaryTeam);
@@ -431,7 +431,7 @@ const graphQLSyncAllChannelMembers = async (serverUrl: string) => {
const modelPromises = await prepareMyChannelsForTeam(operator, '', channels, memberships, undefined, true);
const models = (await Promise.all(modelPromises)).flat();
if (models.length) {
await operator.batchRecords(models, 'graphQLSyncAllChannelMembers');
await operator.batchRecords(models);
}
}

View File

@@ -82,7 +82,7 @@ export async function deferredAppEntryGraphQLActions(
modelPromises.push(operator.handleRole({roles, prepareRecordsOnly: true}));
}
const models = (await Promise.all(modelPromises)).flat();
operator.batchRecords(models, 'deferredAppEntryActions');
operator.batchRecords(models);
setTimeout(() => {
if (result.chData?.channels?.length && result.chData.memberships?.length) {
@@ -217,7 +217,7 @@ export const entryGQL = async (serverUrl: string, currentTeamId?: string, curren
if (!teamData.teams.length) {
initialTeamId = '';
} else if (!initialTeamId || !teamData.teams.find((t) => t.id === currentTeamId && t.delete_at === 0)) {
const teamOrderPreference = getPreferenceValue<string>(prefData.preferences || [], Preferences.CATEGORIES.TEAMS_ORDER, '', '');
const teamOrderPreference = getPreferenceValue(prefData.preferences || [], Preferences.TEAMS_ORDER, '', '') as string;
initialTeamId = selectDefaultTeam(teamData.teams, meData.user.locale, teamOrderPreference, config.ExperimentalPrimaryTeam)?.id || '';
}
const gqlRoles = [

View File

@@ -68,7 +68,7 @@ export async function loginEntry({serverUrl, user, deviceToken}: AfterLoginArgs)
setCurrentTeamAndChannelId(operator, initialTeamId, '');
}
await operator.batchRecords(models, 'loginEntry');
await operator.batchRecords(models);
setTeamLoading(serverUrl, false);
const config = clData.config || {} as ClientConfig;

View File

@@ -3,11 +3,11 @@
import {switchToChannelById} from '@actions/remote/channel';
import {fetchAndSwitchToThread} from '@actions/remote/thread';
import {Screens} from '@constants';
import {Preferences, Screens} from '@constants';
import {getDefaultThemeByAppearance} from '@context/theme';
import DatabaseManager from '@database/manager';
import {getMyChannel} from '@queries/servers/channel';
import {queryThemePreferences} from '@queries/servers/preference';
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
import {getConfig, getCurrentTeamId, getLicense, getWebSocketLastDisconnected, setCurrentTeamAndChannelId} from '@queries/servers/system';
import {getMyTeamById} from '@queries/servers/team';
import {getIsCRTEnabled} from '@queries/servers/thread';
@@ -53,7 +53,7 @@ export async function pushNotificationEntry(serverUrl: string, notification: Not
// When opening the app from a push notification the theme may not be set in the EphemeralStore
// causing the goToScreen to use the Appearance theme instead and that causes the screen background color to potentially
// not match the theme
const themes = await queryThemePreferences(database, teamId).fetch();
const themes = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_THEME, teamId).fetch();
let theme = getDefaultThemeByAppearance();
if (themes.length) {
theme = setThemeDefaults(JSON.parse(themes[0].value) as Theme);
@@ -136,7 +136,7 @@ export async function pushNotificationEntry(serverUrl: string, notification: Not
await NavigationStore.waitUntilScreenHasLoaded(Screens.THREAD);
}
await operator.batchRecords(models, 'pushNotificationEntry');
await operator.batchRecords(models);
setTeamLoading(serverUrl, false);
const {id: currentUserId, locale: currentUserLocale} = (await getCurrentUser(operator.database))!;

View File

@@ -3,7 +3,6 @@
import {DOWNLOAD_TIMEOUT} from '@constants/network';
import NetworkManager from '@managers/network_manager';
import {logDebug} from '@utils/log';
import {forceLogoutIfNecessary} from './session';
@@ -33,11 +32,10 @@ export const uploadFile = (
let client: Client;
try {
client = NetworkManager.getClient(serverUrl);
return {cancel: client.uploadPostAttachment(file, channelId, onProgress, onComplete, onError, skipBytes)};
} catch (error) {
logDebug('uploadFile', error);
return {error: error as ClientError};
}
return {cancel: client.uploadPostAttachment(file, channelId, onProgress, onComplete, onError, skipBytes)};
};
export const fetchPublicLink = async (serverUrl: string, fileId: string) => {

View File

@@ -83,7 +83,7 @@ export const fetchGroupsForChannel = async (serverUrl: string, channelId: string
]);
if (!fetchOnly) {
await operator.batchRecords([...groups, ...groupChannels], 'fetchGroupsForChannel');
await operator.batchRecords([...groups, ...groupChannels]);
}
return {groups, groupChannels};
@@ -110,7 +110,7 @@ export const fetchGroupsForTeam = async (serverUrl: string, teamId: string, fetc
]);
if (!fetchOnly) {
await operator.batchRecords([...groups, ...groupTeams], 'fetchGroupsForTeam');
await operator.batchRecords([...groups, ...groupTeams]);
}
return {groups, groupTeams};
@@ -136,7 +136,7 @@ export const fetchGroupsForMember = async (serverUrl: string, userId: string, fe
]);
if (!fetchOnly) {
await operator.batchRecords([...groups, ...groupMemberships], 'fetchGroupsForMember');
await operator.batchRecords([...groups, ...groupMemberships]);
}
return {groups, groupMemberships};

View File

@@ -128,7 +128,7 @@ export async function createPost(serverUrl: string, post: Partial<Post>, files:
initialPostModels.push(...reactionModels);
}
await operator.batchRecords(initialPostModels, 'createPost - initial');
await operator.batchRecords(initialPostModels);
const isCRTEnabled = await getIsCRTEnabled(database);
@@ -167,7 +167,7 @@ export async function createPost(serverUrl: string, post: Partial<Post>, files:
models.push(...threadModels);
}
}
await operator.batchRecords(models, 'createPost - failure');
await operator.batchRecords(models);
}
return {data: true};
@@ -192,7 +192,7 @@ export async function createPost(serverUrl: string, post: Partial<Post>, files:
models.push(...threadModels);
}
}
await operator.batchRecords(models, 'createPost - success');
await operator.batchRecords(models);
newPost = created;
@@ -237,7 +237,7 @@ export const retryFailedPost = async (serverUrl: string, post: PostModel) => {
p.props = newPost.props;
p.updateAt = timestamp;
});
await operator.batchRecords([post], 'retryFailedPost - first update');
await operator.batchRecords([post]);
const created = await client.createPost(newPost);
const models = await operator.handlePosts({
@@ -253,7 +253,7 @@ export const retryFailedPost = async (serverUrl: string, post: PostModel) => {
models.push(member);
}
}
await operator.batchRecords(models, 'retryFailedPost - success update');
await operator.batchRecords(models);
} catch (error) {
if (isServerError(error) && (
error.server_error_id === ServerErrors.DELETED_ROOT_POST_ERROR ||
@@ -268,7 +268,7 @@ export const retryFailedPost = async (serverUrl: string, post: PostModel) => {
failed: true,
};
});
await operator.batchRecords([post], 'retryFailedPost - error update');
await operator.batchRecords([post]);
}
return {error};
@@ -375,7 +375,7 @@ export async function fetchPosts(serverUrl: string, channelId: string, page = 0,
models.push(...threadModels);
}
}
await operator.batchRecords(models, 'fetchPosts');
await operator.batchRecords(models);
}
return result;
} catch (error) {
@@ -434,7 +434,7 @@ export async function fetchPostsBefore(serverUrl: string, channelId: string, pos
}
}
await operator.batchRecords(models, 'fetchPostsBefore');
await operator.batchRecords(models);
} catch (error) {
logError('FETCH POSTS BEFORE ERROR', error);
}
@@ -489,7 +489,7 @@ export async function fetchPostsSince(serverUrl: string, channelId: string, sinc
models.push(...threadModels);
}
}
await operator.batchRecords(models, 'fetchPostsSince');
await operator.batchRecords(models);
}
return result;
} catch (error) {
@@ -621,7 +621,7 @@ export async function fetchPostThread(serverUrl: string, postId: string, options
models.push(...threadModels);
}
}
await operator.batchRecords(models, 'fetchPostThread');
await operator.batchRecords(models);
}
setFetchingThreadState(postId, false);
return {posts: extractRecordsForTable<PostModel>(posts, MM_TABLES.SERVER.POST)};
@@ -697,7 +697,7 @@ export async function fetchPostsAround(serverUrl: string, channelId: string, pos
models.push(...threadModels);
}
}
await operator.batchRecords(models, 'fetchPostsAround');
await operator.batchRecords(models);
}
return {posts: extractRecordsForTable<PostModel>(posts, MM_TABLES.SERVER.POST)};
@@ -751,7 +751,7 @@ export async function fetchMissingChannelsFromPosts(serverUrl: string, posts: Po
return mdls;
});
if (models.length) {
await operator.batchRecords(models, 'fetchMissingChannelsFromPosts');
await operator.batchRecords(models);
}
}
}
@@ -809,7 +809,7 @@ export async function fetchPostById(serverUrl: string, postId: string, fetchOnly
}
}
await operator.batchRecords(models, 'fetchPostById');
await operator.batchRecords(models);
}
return {post};
@@ -1036,7 +1036,7 @@ export async function fetchSavedPosts(serverUrl: string, teamId?: string, channe
return mdls;
});
await operator.batchRecords(models, 'fetchSavedPosts');
await operator.batchRecords(models);
return {
order,
@@ -1118,7 +1118,7 @@ export async function fetchPinnedPosts(serverUrl: string, channelId: string) {
return mdls;
});
await operator.batchRecords(models, 'fetchPinnedPosts');
await operator.batchRecords(models);
return {
order,

View File

@@ -5,12 +5,14 @@ import {General, Preferences} from '@constants';
import DatabaseManager from '@database/manager';
import NetworkManager from '@managers/network_manager';
import {getChannelById} from '@queries/servers/channel';
import {querySavedPostsPreferences} from '@queries/servers/preference';
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
import {getCurrentUserId} from '@queries/servers/system';
import {getUserIdFromChannelName} from '@utils/user';
import {forceLogoutIfNecessary} from './session';
const {CATEGORY_DIRECT_CHANNEL_SHOW, CATEGORY_GROUP_CHANNEL_SHOW, CATEGORY_FAVORITE_CHANNEL, CATEGORY_SAVED_POST} = Preferences;
export type MyPreferencesRequest = {
preferences?: PreferenceType[];
error?: unknown;
@@ -54,7 +56,7 @@ export const saveFavoriteChannel = async (serverUrl: string, channelId: string,
try {
const userId = await getCurrentUserId(operator.database);
const favPref: PreferenceType = {
category: Preferences.CATEGORIES.FAVORITE_CHANNEL,
category: CATEGORY_FAVORITE_CHANNEL,
name: channelId,
user_id: userId,
value: String(isFavorite),
@@ -75,7 +77,7 @@ export const savePostPreference = async (serverUrl: string, postId: string) => {
const userId = await getCurrentUserId(operator.database);
const pref: PreferenceType = {
user_id: userId,
category: Preferences.CATEGORIES.SAVED_POST,
category: CATEGORY_SAVED_POST,
name: postId,
value: 'true',
};
@@ -114,15 +116,23 @@ export const savePreference = async (serverUrl: string, preferences: PreferenceT
};
export const deleteSavedPost = async (serverUrl: string, postId: string) => {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
let client;
try {
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const client = NetworkManager.getClient(serverUrl);
const userId = await getCurrentUserId(database);
const records = await querySavedPostsPreferences(database, postId).fetch();
client = NetworkManager.getClient(serverUrl);
} catch (error) {
return {error};
}
try {
const userId = await getCurrentUserId(operator.database);
const records = await queryPreferencesByCategoryAndName(operator.database, CATEGORY_SAVED_POST, postId).fetch();
const postPreferenceRecord = records.find((r) => postId === r.name);
const pref = {
user_id: userId,
category: Preferences.CATEGORIES.SAVED_POST,
category: CATEGORY_SAVED_POST,
name: postId,
value: 'true',
};
@@ -151,8 +161,7 @@ export const setDirectChannelVisible = async (serverUrl: string, channelId: stri
const channel = await getChannelById(database, channelId);
if (channel?.type === General.DM_CHANNEL || channel?.type === General.GM_CHANNEL) {
const userId = await getCurrentUserId(database);
const {DIRECT_CHANNEL_SHOW, GROUP_CHANNEL_SHOW} = Preferences.CATEGORIES;
const category = channel.type === General.DM_CHANNEL ? DIRECT_CHANNEL_SHOW : GROUP_CHANNEL_SHOW;
const category = channel.type === General.DM_CHANNEL ? CATEGORY_DIRECT_CHANNEL_SHOW : CATEGORY_GROUP_CHANNEL_SHOW;
const name = channel.type === General.DM_CHANNEL ? getUserIdFromChannelName(userId, channel.name) : channelId;
const pref: PreferenceType = {
user_id: userId,
@@ -176,7 +185,7 @@ export const savePreferredSkinTone = async (serverUrl: string, skinCode: string)
const userId = await getCurrentUserId(database);
const pref: PreferenceType = {
user_id: userId,
category: Preferences.CATEGORIES.EMOJI,
category: Preferences.CATEGORY_EMOJI,
name: Preferences.EMOJI_SKINTONE,
value: skinCode,
};

View File

@@ -51,7 +51,7 @@ export async function addReaction(serverUrl: string, postId: string, emojiName:
models.push(...recent);
}
await operator.batchRecords(models, 'addReaction');
await operator.batchRecords(models);
return {reaction};
}

View File

@@ -9,7 +9,7 @@ import {selectDefaultTeam} from '@helpers/api/team';
import NetworkManager from '@managers/network_manager';
import {prepareCategoriesAndCategoriesChannels} from '@queries/servers/categories';
import {prepareMyChannelsForTeam} from '@queries/servers/channel';
import {prepareMyPreferences, queryDisplayNamePreferences} from '@queries/servers/preference';
import {prepareMyPreferences, queryPreferencesByCategoryAndName} from '@queries/servers/preference';
import {prepareCommonSystemValues, getConfig, getLicense} from '@queries/servers/system';
import {prepareMyTeams} from '@queries/servers/team';
import {getCurrentUser} from '@queries/servers/user';
@@ -60,7 +60,7 @@ export async function retryInitialTeamAndChannel(serverUrl: string) {
// select initial team
if (!clData.error && !prefData.error && !teamData.error) {
const teamOrderPreference = getPreferenceValue<string>(prefData.preferences!, Preferences.CATEGORIES.TEAMS_ORDER, '', '');
const teamOrderPreference = getPreferenceValue(prefData.preferences!, Preferences.TEAMS_ORDER, '', '') as string;
const teamRoles: string[] = [];
const teamMembers = new Set<string>();
@@ -117,7 +117,7 @@ export async function retryInitialTeamAndChannel(serverUrl: string) {
),
])).flat();
await operator.batchRecords(models, 'retryInitialTeamAndChannel');
await operator.batchRecords(models);
const directChannels = chData!.channels!.filter(isDMorGM);
const channelsToFetchProfiles = new Set<Channel>(directChannels);
@@ -157,7 +157,7 @@ export async function retryInitialChannel(serverUrl: string, teamId: string) {
return {error: true};
}
const prefs = await queryDisplayNamePreferences(database, Preferences.NAME_NAME_FORMAT).fetch();
const prefs = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch();
const preferences: PreferenceType[] = prefs.map((p) => ({
category: p.category,
name: p.name,
@@ -196,7 +196,7 @@ export async function retryInitialChannel(serverUrl: string, teamId: string) {
prepareCommonSystemValues(operator, {currentChannelId: initialChannel?.id}),
])).flat();
await operator.batchRecords(models, 'retryInitialChannel');
await operator.batchRecords(models);
const directChannels = chData!.channels!.filter(isDMorGM);
const channelsToFetchProfiles = new Set<Channel>(directChannels);

View File

@@ -103,7 +103,7 @@ export const searchPosts = async (serverUrl: string, teamId: string, params: Pos
return mdls;
});
await operator.batchRecords(models, 'searchPosts');
await operator.batchRecords(models);
return {
order,
posts: postsArray,

View File

@@ -86,7 +86,7 @@ export async function addUserToTeam(serverUrl: string, teamId: string, userId: s
prepareCategoriesAndCategoriesChannels(operator, categories || [], true),
])).flat();
await operator.batchRecords(models, 'addUserToTeam');
await operator.batchRecords(models);
setTeamLoading(serverUrl, false);
loadEventSent = false;
@@ -199,7 +199,7 @@ export async function fetchMyTeams(serverUrl: string, fetchOnly = false): Promis
const models = await Promise.all(modelPromises);
const flattenedModels = models.flat();
if (flattenedModels.length > 0) {
await operator.batchRecords(flattenedModels, 'fetchMyTeams');
await operator.batchRecords(flattenedModels);
}
}
}
@@ -233,7 +233,7 @@ export async function fetchMyTeam(serverUrl: string, teamId: string, fetchOnly =
const models = await Promise.all(modelPromises);
const flattenedModels = models.flat();
if (flattenedModels?.length > 0) {
await operator.batchRecords(flattenedModels, 'fetchMyTeam');
await operator.batchRecords(flattenedModels);
}
}
}
@@ -362,7 +362,7 @@ export async function fetchTeamByName(serverUrl: string, teamName: string, fetch
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (operator) {
const models = await operator.handleTeam({teams: [team], prepareRecordsOnly: true});
await operator.batchRecords(models, 'fetchTeamByName');
await operator.batchRecords(models);
}
}
@@ -441,7 +441,7 @@ export async function handleTeamChange(serverUrl: string, teamId: string) {
}
if (models.length) {
await operator.batchRecords(models, 'handleTeamChange');
await operator.batchRecords(models);
}
DeviceEventEmitter.emit(Events.TEAM_SWITCH, false);

View File

@@ -43,7 +43,7 @@ export async function updateTermsOfServiceStatus(serverUrl: string, id: string,
u.termsOfServiceId = '';
}
});
operator.batchRecords([currentUser], 'updateTermsOfServiceStatus');
operator.batchRecords([currentUser]);
}
return {resp};
} catch (error) {

View File

@@ -360,7 +360,7 @@ export const syncTeamThreads = async (serverUrl: string, teamId: string, prepare
const allNewThreads = await fetchThreads(
serverUrl,
teamId,
{deleted: true, since: syncData.latest + 1},
{deleted: true, since: syncData.latest},
);
if (allNewThreads.error) {
return {error: allNewThreads.error};
@@ -395,7 +395,7 @@ export const syncTeamThreads = async (serverUrl: string, teamId: string, prepare
if (!prepareRecordsOnly && models?.length) {
try {
await operator.batchRecords(models, 'syncTeamThreads');
await operator.batchRecords(models);
} catch (err) {
if (__DEV__) {
throw err;
@@ -460,7 +460,7 @@ export const loadEarlierThreads = async (serverUrl: string, teamId: string, last
if (!prepareRecordsOnly && models?.length) {
try {
await operator.batchRecords(models, 'loadEarlierThreads');
await operator.batchRecords(models);
} catch (err) {
if (__DEV__) {
throw err;

View File

@@ -74,7 +74,7 @@ export const fetchMe = async (serverUrl: string, fetchOnly = false): Promise<MyU
}
};
export async function fetchProfilesInChannel(serverUrl: string, channelId: string, excludeUserId?: string, options?: GetUsersOptions, fetchOnly = false): Promise<ProfilesInChannelRequest> {
export async function fetchProfilesInChannel(serverUrl: string, channelId: string, excludeUserId?: string, fetchOnly = false): Promise<ProfilesInChannelRequest> {
let client: Client;
try {
client = NetworkManager.getClient(serverUrl);
@@ -83,7 +83,7 @@ export async function fetchProfilesInChannel(serverUrl: string, channelId: strin
}
try {
const users = await client.getProfilesInChannel(channelId, options);
const users = await client.getProfilesInChannel(channelId);
const uniqueUsers = Array.from(new Set(users));
const filteredUsers = uniqueUsers.filter((u) => u.id !== excludeUserId);
if (!fetchOnly) {
@@ -102,13 +102,12 @@ export async function fetchProfilesInChannel(serverUrl: string, channelId: strin
modelPromises.push(prepare);
const models = await Promise.all(modelPromises);
await operator.batchRecords(models.flat(), 'fetchProfilesInChannel');
await operator.batchRecords(models.flat());
}
}
return {channelId, users: filteredUsers};
} catch (error) {
logError('fetchProfilesInChannel', error);
forceLogoutIfNecessary(serverUrl, error as ClientError);
return {channelId, error};
}
@@ -178,7 +177,7 @@ export async function fetchProfilesInGroupChannels(serverUrl: string, groupChann
}
const models = await Promise.all(modelPromises);
await operator.batchRecords(models.flat(), 'fetchProfilesInGroupChannels');
await operator.batchRecords(models.flat());
}
return {data};
@@ -199,7 +198,7 @@ export async function fetchProfilesPerChannels(serverUrl: string, channelIds: st
const data: ProfilesInChannelRequest[] = [];
for await (const cIds of channels) {
const requests = cIds.map((id) => fetchProfilesInChannel(serverUrl, id, excludeUserId, undefined, true));
const requests = cIds.map((id) => fetchProfilesInChannel(serverUrl, id, excludeUserId, true));
const response = await Promise.all(requests);
data.push(...response);
}
@@ -230,7 +229,7 @@ export async function fetchProfilesPerChannels(serverUrl: string, channelIds: st
}
const models = await Promise.all(modelPromises);
await operator.batchRecords(models.flat(), 'fetchProfilesPerChannels');
await operator.batchRecords(models.flat());
}
return {data};
@@ -366,7 +365,7 @@ export async function fetchStatusByIds(serverUrl: string, userIds: string[], fet
user.prepareStatus(status?.status || General.OFFLINE);
}
await operator.batchRecords(users, 'fetchStatusByIds');
await operator.batchRecords(users);
}
}
@@ -531,37 +530,7 @@ export const fetchProfilesInTeam = async (serverUrl: string, teamId: string, pag
}
};
export const fetchProfilesNotInChannel = async (
serverUrl: string,
teamId: string,
channelId: string,
groupConstrained: boolean,
page = 0,
perPage: number = General.PROFILE_CHUNK_SIZE,
fetchOnly = false,
) => {
try {
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const client = NetworkManager.getClient(serverUrl);
const users = await client.getProfilesNotInChannel(teamId, channelId, groupConstrained, page, perPage);
if (!fetchOnly && users.length) {
const currentUserId = await getCurrentUserId(operator.database);
const toStore = removeUserFromList(currentUserId, users);
await operator.handleUsers({
users: toStore,
prepareRecordsOnly: false,
});
}
return {users};
} catch (error) {
forceLogoutIfNecessary(serverUrl, error as ClientError);
return {error};
}
};
export const searchProfiles = async (serverUrl: string, term: string, options: SearchUserOptions, fetchOnly = false) => {
export const searchProfiles = async (serverUrl: string, term: string, options: any = {}, fetchOnly = false) => {
let client: Client;
try {
client = NetworkManager.getClient(serverUrl);
@@ -594,7 +563,6 @@ export const searchProfiles = async (serverUrl: string, term: string, options: S
return {data: users};
} catch (error) {
logError('searchProfiles', error);
forceLogoutIfNecessary(serverUrl, error as ClientError);
return {error};
}
@@ -671,7 +639,7 @@ export async function updateAllUsersSince(serverUrl: string, since: number, fetc
modelsToBatch.push(...models);
}
await operator.batchRecords(modelsToBatch, 'updateAllUsersSince');
await operator.batchRecords(modelsToBatch);
}
} catch {
// Do nothing
@@ -707,7 +675,7 @@ export async function updateUsersNoLongerVisible(serverUrl: string, prepareRecor
}
}
if (models.length && !prepareRecordsOnly) {
serverDatabase.operator.batchRecords(models, 'updateUsersNoLongerVisible');
serverDatabase.operator.batchRecords(models);
}
} catch (error) {
forceLogoutIfNecessary(serverUrl, error as ClientError);
@@ -947,7 +915,7 @@ export const fetchTeamAndChannelMembership = async (serverUrl: string, userId: s
}
const models = await Promise.all(modelPromises);
await operator.batchRecords(models.flat(), 'fetchTeamAndChannelMembership');
await operator.batchRecords(models.flat());
return {error: undefined};
} catch (error) {
return {error};

View File

@@ -96,7 +96,7 @@ export async function handleCategoryOrderUpdatedEvent(serverUrl: string, msg: We
c.sortOrder = order.findIndex(findOrder);
});
});
await operator.batchRecords(categories, 'handleCategoryOrderUpdatedEvent');
await operator.batchRecords(categories);
}
} catch (e) {
logError('Category WS: handleCategoryOrderUpdatedEvent', e, msg);

View File

@@ -57,7 +57,7 @@ export async function handleChannelCreatedEvent(serverUrl: string, msg: any) {
}
}
}
operator.batchRecords(models, 'handleChannelCreatedEvent');
operator.batchRecords(models);
} catch {
// do nothing
}
@@ -109,7 +109,7 @@ export async function handleChannelUpdatedEvent(serverUrl: string, msg: any) {
if (infoModel.model) {
models.push(...infoModel.model);
}
operator.batchRecords(models, 'handleChannelUpdatedEvent');
operator.batchRecords(models);
} catch {
// Do nothing
}
@@ -165,7 +165,7 @@ export async function handleChannelMemberUpdatedEvent(serverUrl: string, msg: an
if (rolesRequest.roles?.length) {
models.push(...await operator.handleRole({roles: rolesRequest.roles, prepareRecordsOnly: true}));
}
operator.batchRecords(models, 'handleChannelMemberUpdatedEvent');
operator.batchRecords(models);
} catch {
// do nothing
}
@@ -235,7 +235,7 @@ export async function handleDirectAddedEvent(serverUrl: string, msg: WebSocketMe
models.push(...userModels);
}
operator.batchRecords(models, 'handleDirectAddedEvent');
operator.batchRecords(models);
} catch {
// do nothing
}
@@ -267,7 +267,7 @@ export async function handleUserAddedToChannelEvent(serverUrl: string, msg: any)
const prepareModels = await Promise.all(prepare);
const flattenedModels = prepareModels.flat();
if (flattenedModels?.length > 0) {
await operator.batchRecords(flattenedModels, 'handleUserAddedToChannelEvent - prepareMyChannelsForTeam');
await operator.batchRecords(flattenedModels);
}
}
@@ -308,7 +308,7 @@ export async function handleUserAddedToChannelEvent(serverUrl: string, msg: any)
}
if (models.length) {
await operator.batchRecords(models, 'handleUserAddedToChannelEvent');
await operator.batchRecords(models);
}
await fetchChannelStats(serverUrl, channelId, false);
@@ -356,7 +356,7 @@ export async function handleUserRemovedFromChannelEvent(serverUrl: string, msg:
}
}
operator.batchRecords(models, 'handleUserRemovedFromChannelEvent');
operator.batchRecords(models);
} catch (error) {
logDebug('cannot handle user removed from channel websocket event', error);
}

View File

@@ -150,7 +150,7 @@ async function doReconnect(serverUrl: string) {
await handleEntryAfterLoadNavigation(serverUrl, teamData.memberships || [], chData?.memberships || [], currentTeam?.id || '', currentChannel?.id || '', initialTeamId, initialChannelId);
const dt = Date.now();
await operator.batchRecords(models, 'doReconnect');
await operator.batchRecords(models);
logInfo('WEBSOCKET RECONNECT MODELS BATCHING TOOK', `${Date.now() - dt}ms`);
setTeamLoading(serverUrl, false);

View File

@@ -176,7 +176,7 @@ export async function handleNewPostEvent(serverUrl: string, msg: WebSocketMessag
models.push(...postModels);
operator.batchRecords(models, 'handleNewPostEvent');
operator.batchRecords(models);
}
export async function handlePostEdited(serverUrl: string, msg: WebSocketMessage) {
@@ -220,7 +220,7 @@ export async function handlePostEdited(serverUrl: string, msg: WebSocketMessage)
});
models.push(...postModels);
operator.batchRecords(models, 'handlePostEdited');
operator.batchRecords(models);
}
export async function handlePostDeleted(serverUrl: string, msg: WebSocketMessage) {
@@ -263,7 +263,7 @@ export async function handlePostDeleted(serverUrl: string, msg: WebSocketMessage
}
if (models.length) {
await operator.batchRecords(models, 'handlePostDeleted');
await operator.batchRecords(models);
}
} catch {
// Do nothing

View File

@@ -107,7 +107,7 @@ async function handleSavePostAdded(serverUrl: string, preferences: PreferenceTyp
return;
}
const savedPosts = preferences.filter((p) => p.category === Preferences.CATEGORIES.SAVED_POST);
const savedPosts = preferences.filter((p) => p.category === Preferences.CATEGORY_SAVED_POST);
for await (const saved of savedPosts) {
const post = await getPostById(database, saved.name);
if (!post) {

View File

@@ -66,7 +66,7 @@ export async function handleUserRoleUpdatedEvent(serverUrl: string, msg: WebSock
models.push(user);
}
await operator.batchRecords(models, 'handleUserRoleUpdatedEvent');
await operator.batchRecords(models);
}
export async function handleTeamMemberRoleUpdatedEvent(serverUrl: string, msg: WebSocketMessage): Promise<void> {
@@ -118,7 +118,7 @@ export async function handleTeamMemberRoleUpdatedEvent(serverUrl: string, msg: W
});
models.push(...teamMembership);
await operator.batchRecords(models, 'handleTeamMemberRoleUpdatedEvent');
await operator.batchRecords(models);
} catch {
// do nothing
}

View File

@@ -162,5 +162,5 @@ const fetchAndStoreJoinedTeamInfo = async (serverUrl: string, operator: ServerDa
}
const models = await Promise.all(modelPromises);
await operator.batchRecords(models.flat(), 'fetchAndStoreJoinedTeamInfo');
await operator.batchRecords(models.flat());
};

View File

@@ -10,7 +10,7 @@ import DatabaseManager from '@database/manager';
import {getTeammateNameDisplaySetting} from '@helpers/api/preference';
import WebsocketManager from '@managers/websocket_manager';
import {queryChannelsByTypes, queryUserChannelsByTypes} from '@queries/servers/channel';
import {queryDisplayNamePreferences} from '@queries/servers/preference';
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
import {getConfig, getLicense} from '@queries/servers/system';
import {getCurrentUser} from '@queries/servers/user';
import {displayUsername} from '@utils/user';
@@ -71,7 +71,7 @@ export async function handleUserUpdatedEvent(serverUrl: string, msg: WebSocketMe
modelsToBatch.push(...userModel);
try {
await operator.batchRecords(modelsToBatch, 'handleUserUpdatedEvent');
await operator.batchRecords(modelsToBatch);
} catch {
// do nothing
}
@@ -91,7 +91,7 @@ export async function handleUserTypingEvent(serverUrl: string, msg: WebSocketMes
const {users, existingUsers} = await fetchUsersByIds(serverUrl, [msg.data.user_id]);
const user = users?.[0] || existingUsers?.[0];
const namePreference = await queryDisplayNamePreferences(database, Preferences.NAME_NAME_FORMAT).fetch();
const namePreference = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch();
const teammateDisplayNameSetting = getTeammateNameDisplaySetting(namePreference, config.LockTeammateNameDisplay, config.TeammateNameDisplay, license);
const currentUser = await getCurrentUser(database);
const username = displayUsername(user, currentUser?.locale, teammateDisplayNameSetting);

View File

@@ -3,15 +3,13 @@
import {buildQueryString} from '@utils/helpers';
import type ClientBase from './base';
export interface ClientAppsMix {
executeAppCall: <Res = unknown>(call: AppCallRequest, trackAsSubmit: boolean) => Promise<AppCallResponse<Res>>;
getAppsBindings: (userID: string, channelID: string, teamID: string) => Promise<AppBinding[]>;
}
const ClientApps = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
executeAppCall = async (call: AppCallRequest, trackAsSubmit: boolean) => {
const ClientApps = (superclass: any) => class extends superclass {
executeAppCall = async <Res = unknown>(call: AppCallRequest, trackAsSubmit: boolean): Promise<AppCallResponse<Res>> => {
const callCopy = {
...call,
context: {

View File

@@ -26,7 +26,6 @@ export default class ClientBase {
requestHeaders: {[x: string]: string} = {};
serverVersion = '';
urlVersion = '/api/v4';
enableLogging = false;
constructor(apiClient: APIClientInterface, serverUrl: string, bearerToken?: string, csrfToken?: string) {
this.apiClient = apiClient;
@@ -46,10 +45,6 @@ export default class ClientBase {
}
}
getBaseRoute() {
return this.apiClient.baseUrl || '';
}
getAbsoluteUrl(baseUrl?: string) {
if (typeof baseUrl !== 'string' || !baseUrl.startsWith('/')) {
return baseUrl;
@@ -308,7 +303,7 @@ export default class ClientBase {
}
if (response.ok) {
return returnDataOnly ? (response.data || {}) : response;
return returnDataOnly ? response.data : response;
}
throw new ClientError(this.apiClient.baseUrl, {

View File

@@ -1,8 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type ClientBase from './base';
export interface ClientCategoriesMix {
getCategories: (userId: string, teamId: string) => Promise<CategoriesWithOrder>;
getCategoriesOrder: (userId: string, teamId: string) => Promise<string[]>;
@@ -10,7 +8,7 @@ export interface ClientCategoriesMix {
updateChannelCategories: (userId: string, teamId: string, categories: CategoryWithChannels[]) => Promise<CategoriesWithOrder>;
}
const ClientCategories = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
const ClientCategories = (superclass: any) => class extends superclass {
getCategories = async (userId: string, teamId: string) => {
return this.doFetch(
`${this.getCategoriesRoute(userId, teamId)}`,

View File

@@ -5,8 +5,6 @@ import {buildQueryString} from '@utils/helpers';
import {PER_PAGE_DEFAULT} from './constants';
import type ClientBase from './base';
export interface ClientChannelsMix {
getAllChannels: (page?: number, perPage?: number, notAssociatedToGroup?: string, excludeDefaultChannels?: boolean, includeTotalCount?: boolean) => Promise<any>;
createChannel: (channel: Channel) => Promise<Channel>;
@@ -42,11 +40,9 @@ export interface ClientChannelsMix {
searchChannels: (teamId: string, term: string) => Promise<Channel[]>;
searchArchivedChannels: (teamId: string, term: string) => Promise<Channel[]>;
searchAllChannels: (term: string, teamIds: string[], archivedOnly?: boolean) => Promise<Channel[]>;
updateChannelMemberSchemeRoles: (channelId: string, userId: string, isSchemeUser: boolean, isSchemeAdmin: boolean) => Promise<any>;
getMemberInChannel: (channelId: string, userId: string) => Promise<any>;
}
const ClientChannels = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
const ClientChannels = (superclass: any) => class extends superclass {
getAllChannels = async (page = 0, perPage = PER_PAGE_DEFAULT, notAssociatedToGroup = '', excludeDefaultChannels = false, includeTotalCount = false) => {
const queryData = {
page,
@@ -62,7 +58,7 @@ const ClientChannels = <TBase extends Constructor<ClientBase>>(superclass: TBase
};
createChannel = async (channel: Channel) => {
this.analytics?.trackAPI('api_channels_create', {team_id: channel.team_id});
this.analytics.trackAPI('api_channels_create', {team_id: channel.team_id});
return this.doFetch(
`${this.getChannelsRoute()}`,
@@ -71,7 +67,7 @@ const ClientChannels = <TBase extends Constructor<ClientBase>>(superclass: TBase
};
createDirectChannel = async (userIds: string[]) => {
this.analytics?.trackAPI('api_channels_create_direct');
this.analytics.trackAPI('api_channels_create_direct');
return this.doFetch(
`${this.getChannelsRoute()}/direct`,
@@ -80,7 +76,7 @@ const ClientChannels = <TBase extends Constructor<ClientBase>>(superclass: TBase
};
createGroupChannel = async (userIds: string[]) => {
this.analytics?.trackAPI('api_channels_create_group');
this.analytics.trackAPI('api_channels_create_group');
return this.doFetch(
`${this.getChannelsRoute()}/group`,
@@ -89,7 +85,7 @@ const ClientChannels = <TBase extends Constructor<ClientBase>>(superclass: TBase
};
deleteChannel = async (channelId: string) => {
this.analytics?.trackAPI('api_channels_delete', {channel_id: channelId});
this.analytics.trackAPI('api_channels_delete', {channel_id: channelId});
return this.doFetch(
`${this.getChannelRoute(channelId)}`,
@@ -98,7 +94,7 @@ const ClientChannels = <TBase extends Constructor<ClientBase>>(superclass: TBase
};
unarchiveChannel = async (channelId: string) => {
this.analytics?.trackAPI('api_channels_unarchive', {channel_id: channelId});
this.analytics.trackAPI('api_channels_unarchive', {channel_id: channelId});
return this.doFetch(
`${this.getChannelRoute(channelId)}/restore`,
@@ -107,7 +103,7 @@ const ClientChannels = <TBase extends Constructor<ClientBase>>(superclass: TBase
};
updateChannel = async (channel: Channel) => {
this.analytics?.trackAPI('api_channels_update', {channel_id: channel.id});
this.analytics.trackAPI('api_channels_update', {channel_id: channel.id});
return this.doFetch(
`${this.getChannelRoute(channel.id)}`,
@@ -120,7 +116,7 @@ const ClientChannels = <TBase extends Constructor<ClientBase>>(superclass: TBase
};
updateChannelPrivacy = async (channelId: string, privacy: any) => {
this.analytics?.trackAPI('api_channels_update_privacy', {channel_id: channelId, privacy});
this.analytics.trackAPI('api_channels_update_privacy', {channel_id: channelId, privacy});
return this.doFetch(
`${this.getChannelRoute(channelId)}/privacy`,
@@ -129,7 +125,7 @@ const ClientChannels = <TBase extends Constructor<ClientBase>>(superclass: TBase
};
patchChannel = async (channelId: string, channelPatch: Partial<Channel>) => {
this.analytics?.trackAPI('api_channels_patch', {channel_id: channelId});
this.analytics.trackAPI('api_channels_patch', {channel_id: channelId});
return this.doFetch(
`${this.getChannelRoute(channelId)}/patch`,
@@ -138,7 +134,7 @@ const ClientChannels = <TBase extends Constructor<ClientBase>>(superclass: TBase
};
updateChannelNotifyProps = async (props: ChannelNotifyProps & {channel_id: string; user_id: string}) => {
this.analytics?.trackAPI('api_users_update_channel_notifications', {channel_id: props.channel_id});
this.analytics.trackAPI('api_users_update_channel_notifications', {channel_id: props.channel_id});
return this.doFetch(
`${this.getChannelMemberRoute(props.channel_id, props.user_id)}/notify_props`,
@@ -147,7 +143,7 @@ const ClientChannels = <TBase extends Constructor<ClientBase>>(superclass: TBase
};
getChannel = async (channelId: string) => {
this.analytics?.trackAPI('api_channel_get', {channel_id: channelId});
this.analytics.trackAPI('api_channel_get', {channel_id: channelId});
return this.doFetch(
`${this.getChannelRoute(channelId)}`,
@@ -163,7 +159,7 @@ const ClientChannels = <TBase extends Constructor<ClientBase>>(superclass: TBase
};
getChannelByNameAndTeamName = async (teamName: string, channelName: string, includeDeleted = false) => {
this.analytics?.trackAPI('api_channel_get_by_name_and_teamName', {channel_name: channelName, team_name: teamName, include_deleted: includeDeleted});
this.analytics.trackAPI('api_channel_get_by_name_and_teamName', {channel_name: channelName, team_name: teamName, include_deleted: includeDeleted});
return this.doFetch(
`${this.getTeamNameRoute(teamName)}/channels/name/${channelName}?include_deleted=${includeDeleted}`,
@@ -245,7 +241,7 @@ const ClientChannels = <TBase extends Constructor<ClientBase>>(superclass: TBase
};
addToChannel = async (userId: string, channelId: string, postRootId = '') => {
this.analytics?.trackAPI('api_channels_add_member', {channel_id: channelId});
this.analytics.trackAPI('api_channels_add_member', {channel_id: channelId});
const member = {user_id: userId, channel_id: channelId, post_root_id: postRootId};
return this.doFetch(
@@ -255,7 +251,7 @@ const ClientChannels = <TBase extends Constructor<ClientBase>>(superclass: TBase
};
removeFromChannel = async (userId: string, channelId: string) => {
this.analytics?.trackAPI('api_channels_remove_member', {channel_id: channelId});
this.analytics.trackAPI('api_channels_remove_member', {channel_id: channelId});
return this.doFetch(
`${this.getChannelMemberRoute(channelId, userId)}`,
@@ -331,25 +327,6 @@ const ClientChannels = <TBase extends Constructor<ClientBase>>(superclass: TBase
{method: 'post', body},
);
};
// Update a channel member's scheme_admin/scheme_user properties. Typically
// this should either be scheme_admin=false, scheme_user=true for ordinary
// channel member, or scheme_admin=true, scheme_user=true for a channel
// admin.
updateChannelMemberSchemeRoles = (channelId: string, userId: string, isSchemeUser: boolean, isSchemeAdmin: boolean) => {
const body = {scheme_user: isSchemeUser, scheme_admin: isSchemeAdmin};
return this.doFetch(
`${this.getChannelMembersRoute(channelId)}/${userId}/schemeRoles`,
{method: 'put', body},
);
};
getMemberInChannel = (channelId: string, userId: string) => {
return this.doFetch(
`${this.getChannelMembersRoute(channelId)}/${userId}`,
{method: 'get'},
);
};
};
export default ClientChannels;

View File

@@ -5,8 +5,6 @@ import {buildQueryString} from '@utils/helpers';
import {PER_PAGE_DEFAULT} from './constants';
import type ClientBase from './base';
export interface ClientEmojisMix {
getCustomEmoji: (id: string) => Promise<CustomEmoji>;
getCustomEmojiByName: (name: string) => Promise<CustomEmoji>;
@@ -17,7 +15,7 @@ export interface ClientEmojisMix {
autocompleteCustomEmoji: (name: string) => Promise<CustomEmoji[]>;
}
const ClientEmojis = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
const ClientEmojis = (superclass: any) => class extends superclass {
getCustomEmoji = async (id: string) => {
return this.doFetch(
`${this.getEmojisRoute()}/${id}`,

View File

@@ -3,7 +3,6 @@
import {toMilliseconds} from '@utils/datetime';
import type ClientBase from './base';
import type {ClientResponse, ClientResponseError, ProgressPromise, UploadRequestOptions} from '@mattermost/react-native-network-client';
export interface ClientFilesMix {
@@ -23,7 +22,7 @@ export interface ClientFilesMix {
searchFilesWithParams: (teamId: string, FileSearchParams: string) => Promise<FileSearchRequest>;
}
const ClientFiles = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
const ClientFiles = (superclass: any) => class extends superclass {
getFileUrl(fileId: string, timestamp: number) {
let url = `${this.apiClient.baseUrl}${this.getFileRoute(fileId)}`;
if (timestamp) {
@@ -77,17 +76,13 @@ const ClientFiles = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
},
timeoutInterval: toMilliseconds({minutes: 3}),
};
if (!file.localPath) {
throw new Error('file does not have local path defined');
}
const promise = this.apiClient.upload(url, file.localPath, options) as ProgressPromise<ClientResponse>;
promise.progress!(onProgress).then(onComplete).catch(onError);
return promise.cancel!;
};
searchFilesWithParams = async (teamId: string, params: FileSearchParams) => {
this.analytics?.trackAPI('api_files_search');
this.analytics.trackAPI('api_files_search');
const endpoint = teamId ? `${this.getTeamRoute(teamId)}/files/search` : `${this.getFilesRoute()}/search`;
return this.doFetch(endpoint, {method: 'post', body: params});
};

View File

@@ -6,8 +6,6 @@ import {buildQueryString} from '@utils/helpers';
import {PER_PAGE_DEFAULT} from './constants';
import ClientError from './error';
import type ClientBase from './base';
type PoliciesResponse<T> = {
policies: T[];
total_count: number;
@@ -27,7 +25,7 @@ export interface ClientGeneralMix {
getRedirectLocation: (urlParam: string) => Promise<Record<string, string>>;
}
const ClientGeneral = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
const ClientGeneral = (superclass: any) => class extends superclass {
getOpenGraphMetadata = async (url: string) => {
return this.doFetch(
`${this.urlVersion}/opengraph`,
@@ -51,7 +49,7 @@ const ClientGeneral = <TBase extends Constructor<ClientBase>>(superclass: TBase)
const url = `${this.urlVersion}/logs`;
if (!this.enableLogging) {
throw new ClientError(this.apiClient.baseUrl, {
throw new ClientError(this.client.baseUrl, {
message: 'Logging disabled.',
url,
});

View File

@@ -5,8 +5,6 @@ import {buildQueryString} from '@utils/helpers';
import {PER_PAGE_DEFAULT} from './constants';
import type ClientBase from './base';
export interface ClientGroupsMix {
getGroup: (id: string) => Promise<Group>;
getGroups: (params: {query?: string; filterAllowReference?: boolean; page?: number; perPage?: number; since?: number; includeMemberCount?: boolean}) => Promise<Group[]>;
@@ -18,7 +16,7 @@ export interface ClientGroupsMix {
getAllTeamsAssociatedToGroup: (groupId: string, filterAllowReference?: boolean) => Promise<{groupTeams: GroupTeam[]}>;
}
const ClientGroups = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
const ClientGroups = (superclass: any) => class extends superclass {
getGroup = async (id: string) => {
return this.doFetch(
`${this.urlVersion}/groups/${id}`,

View File

@@ -5,8 +5,6 @@ import {buildQueryString} from '@utils/helpers';
import {PER_PAGE_DEFAULT} from './constants';
import type ClientBase from './base';
export interface ClientIntegrationsMix {
getCommandsList: (teamId: string) => Promise<Command[]>;
getCommandAutocompleteSuggestionsList: (userInput: string, teamId: string, channelId: string, rootId?: string) => Promise<AutocompleteSuggestion[]>;
@@ -16,7 +14,7 @@ export interface ClientIntegrationsMix {
submitInteractiveDialog: (data: DialogSubmission) => Promise<any>;
}
const ClientIntegrations = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
const ClientIntegrations = (superclass: any) => class extends superclass {
getCommandsList = async (teamId: string) => {
return this.doFetch(
`${this.getCommandsRoute()}?team_id=${teamId}`,
@@ -39,7 +37,7 @@ const ClientIntegrations = <TBase extends Constructor<ClientBase>>(superclass: T
};
executeCommand = async (command: string, commandArgs = {}) => {
this.analytics?.trackAPI('api_integrations_used');
this.analytics.trackAPI('api_integrations_used');
return this.doFetch(
`${this.getCommandsRoute()}/execute`,
@@ -48,7 +46,7 @@ const ClientIntegrations = <TBase extends Constructor<ClientBase>>(superclass: T
};
addCommand = async (command: Command) => {
this.analytics?.trackAPI('api_integrations_created');
this.analytics.trackAPI('api_integrations_created');
return this.doFetch(
`${this.getCommandsRoute()}`,
@@ -57,7 +55,7 @@ const ClientIntegrations = <TBase extends Constructor<ClientBase>>(superclass: T
};
submitInteractiveDialog = async (data: DialogSubmission) => {
this.analytics?.trackAPI('api_interactive_messages_dialog_submitted');
this.analytics.trackAPI('api_interactive_messages_dialog_submitted');
return this.doFetch(
`${this.urlVersion}/actions/dialogs/submit`,
{method: 'post', body: data},

View File

@@ -3,13 +3,11 @@
import {General} from '@constants';
import type ClientBase from './base';
export interface ClientNPSMix {
npsGiveFeedbackAction: () => Promise<Post>;
}
const ClientNPS = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
const ClientNPS = (superclass: any) => class extends superclass {
npsGiveFeedbackAction = async () => {
return this.doFetch(
`${this.getPluginRoute(General.NPS_PLUGIN_ID)}/api/v1/give_feedback`,

View File

@@ -1,13 +1,11 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type ClientBase from './base';
export interface ClientPluginsMix {
getPluginsManifests: () => Promise<ClientPluginManifest[]>;
}
const ClientPlugins = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
const ClientPlugins = (superclass: any) => class extends superclass {
getPluginsManifests = async () => {
return this.doFetch(
`${this.getPluginsRoute()}/webapp`,

View File

@@ -5,8 +5,6 @@ import {buildQueryString} from '@utils/helpers';
import {PER_PAGE_DEFAULT} from './constants';
import type ClientBase from './base';
export interface ClientPostsMix {
createPost: (post: Post) => Promise<Post>;
updatePost: (post: Post) => Promise<Post>;
@@ -33,12 +31,12 @@ export interface ClientPostsMix {
doPostActionWithCookie: (postId: string, actionId: string, actionCookie: string, selectedOption?: string) => Promise<any>;
}
const ClientPosts = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
const ClientPosts = (superclass: any) => class extends superclass {
createPost = async (post: Post) => {
this.analytics?.trackAPI('api_posts_create', {channel_id: post.channel_id});
this.analytics.trackAPI('api_posts_create', {channel_id: post.channel_id});
if (post.root_id != null && post.root_id !== '') {
this.analytics?.trackAPI('api_posts_replied', {channel_id: post.channel_id});
this.analytics.trackAPI('api_posts_replied', {channel_id: post.channel_id});
}
return this.doFetch(
@@ -48,7 +46,7 @@ const ClientPosts = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
updatePost = async (post: Post) => {
this.analytics?.trackAPI('api_posts_update', {channel_id: post.channel_id});
this.analytics.trackAPI('api_posts_update', {channel_id: post.channel_id});
return this.doFetch(
`${this.getPostRoute(post.id)}`,
@@ -64,7 +62,7 @@ const ClientPosts = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
patchPost = async (postPatch: Partial<Post> & {id: string}) => {
this.analytics?.trackAPI('api_posts_patch', {channel_id: postPatch.channel_id});
this.analytics.trackAPI('api_posts_patch', {channel_id: postPatch.channel_id});
return this.doFetch(
`${this.getPostRoute(postPatch.id)}/patch`,
@@ -73,7 +71,7 @@ const ClientPosts = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
deletePost = async (postId: string) => {
this.analytics?.trackAPI('api_posts_delete');
this.analytics.trackAPI('api_posts_delete');
return this.doFetch(
`${this.getPostRoute(postId)}`,
@@ -81,7 +79,7 @@ const ClientPosts = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
);
};
getPostThread = (postId: string, options: FetchPaginatedThreadOptions) => {
getPostThread = (postId: string, options: FetchPaginatedThreadOptions): Promise<PostResponse> => {
const {
fetchThreads = true,
collapsedThreads = false,
@@ -112,7 +110,7 @@ const ClientPosts = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
getPostsBefore = async (channelId: string, postId: string, page = 0, perPage = PER_PAGE_DEFAULT, collapsedThreads = false, collapsedThreadsExtended = false) => {
this.analytics?.trackAPI('api_posts_get_before', {channel_id: channelId});
this.analytics.trackAPI('api_posts_get_before', {channel_id: channelId});
return this.doFetch(
`${this.getChannelRoute(channelId)}/posts${buildQueryString({before: postId, page, per_page: perPage, collapsedThreads, collapsedThreadsExtended})}`,
@@ -121,7 +119,7 @@ const ClientPosts = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
getPostsAfter = async (channelId: string, postId: string, page = 0, perPage = PER_PAGE_DEFAULT, collapsedThreads = false, collapsedThreadsExtended = false) => {
this.analytics?.trackAPI('api_posts_get_after', {channel_id: channelId});
this.analytics.trackAPI('api_posts_get_after', {channel_id: channelId});
return this.doFetch(
`${this.getChannelRoute(channelId)}/posts${buildQueryString({after: postId, page, per_page: perPage, collapsedThreads, collapsedThreadsExtended})}`,
@@ -137,7 +135,7 @@ const ClientPosts = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
getSavedPosts = async (userId: string, channelId = '', teamId = '', page = 0, perPage = PER_PAGE_DEFAULT) => {
this.analytics?.trackAPI('api_posts_get_flagged', {team_id: teamId});
this.analytics.trackAPI('api_posts_get_flagged', {team_id: teamId});
return this.doFetch(
`${this.getUserRoute(userId)}/posts/flagged${buildQueryString({channel_id: channelId, team_id: teamId, page, per_page: perPage})}`,
@@ -146,7 +144,7 @@ const ClientPosts = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
getPinnedPosts = async (channelId: string) => {
this.analytics?.trackAPI('api_posts_get_pinned', {channel_id: channelId});
this.analytics.trackAPI('api_posts_get_pinned', {channel_id: channelId});
return this.doFetch(
`${this.getChannelRoute(channelId)}/pinned`,
{method: 'get'},
@@ -154,7 +152,7 @@ const ClientPosts = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
markPostAsUnread = async (userId: string, postId: string) => {
this.analytics?.trackAPI('api_post_set_unread_post');
this.analytics.trackAPI('api_post_set_unread_post');
// collapsed_threads_supported is not based on user preferences but to know if "CLIENT" supports CRT
const body = JSON.stringify({collapsed_threads_supported: true});
@@ -166,7 +164,7 @@ const ClientPosts = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
pinPost = async (postId: string) => {
this.analytics?.trackAPI('api_posts_pin');
this.analytics.trackAPI('api_posts_pin');
return this.doFetch(
`${this.getPostRoute(postId)}/pin`,
@@ -175,7 +173,7 @@ const ClientPosts = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
unpinPost = async (postId: string) => {
this.analytics?.trackAPI('api_posts_unpin');
this.analytics.trackAPI('api_posts_unpin');
return this.doFetch(
`${this.getPostRoute(postId)}/unpin`,
@@ -184,7 +182,7 @@ const ClientPosts = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
addReaction = async (userId: string, postId: string, emojiName: string) => {
this.analytics?.trackAPI('api_reactions_save', {post_id: postId});
this.analytics.trackAPI('api_reactions_save', {post_id: postId});
return this.doFetch(
`${this.getReactionsRoute()}`,
@@ -193,7 +191,7 @@ const ClientPosts = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
removeReaction = async (userId: string, postId: string, emojiName: string) => {
this.analytics?.trackAPI('api_reactions_delete', {post_id: postId});
this.analytics.trackAPI('api_reactions_delete', {post_id: postId});
return this.doFetch(
`${this.getUserRoute(userId)}/posts/${postId}/reactions/${emojiName}`,
@@ -209,7 +207,7 @@ const ClientPosts = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
searchPostsWithParams = async (teamId: string, params: PostSearchParams) => {
this.analytics?.trackAPI('api_posts_search');
this.analytics.trackAPI('api_posts_search');
const endpoint = teamId ? `${this.getTeamRoute(teamId)}/posts/search` : `${this.getPostsRoute()}/search`;
return this.doFetch(endpoint, {method: 'post', body: params});
};
@@ -224,9 +222,9 @@ const ClientPosts = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
doPostActionWithCookie = async (postId: string, actionId: string, actionCookie: string, selectedOption = '') => {
if (selectedOption) {
this.analytics?.trackAPI('api_interactive_messages_menu_selected');
this.analytics.trackAPI('api_interactive_messages_menu_selected');
} else {
this.analytics?.trackAPI('api_interactive_messages_button_clicked');
this.analytics.trackAPI('api_interactive_messages_button_clicked');
}
const msg: any = {

View File

@@ -1,17 +1,15 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type ClientBase from './base';
export interface ClientPreferencesMix {
savePreferences: (userId: string, preferences: PreferenceType[]) => Promise<any>;
deletePreferences: (userId: string, preferences: PreferenceType[]) => Promise<any>;
getMyPreferences: () => Promise<PreferenceType[]>;
}
const ClientPreferences = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
const ClientPreferences = (superclass: any) => class extends superclass {
savePreferences = async (userId: string, preferences: PreferenceType[]) => {
this.analytics?.trackAPI('action_posts_flag');
this.analytics.trackAPI('action_posts_flag');
return this.doFetch(
`${this.getPreferencesRoute(userId)}`,
{method: 'put', body: preferences},
@@ -26,7 +24,7 @@ const ClientPreferences = <TBase extends Constructor<ClientBase>>(superclass: TB
};
deletePreferences = async (userId: string, preferences: PreferenceType[]) => {
this.analytics?.trackAPI('action_posts_unflag');
this.analytics.trackAPI('action_posts_unflag');
return this.doFetch(
`${this.getPreferencesRoute(userId)}/delete`,
{method: 'post', body: preferences},

View File

@@ -5,8 +5,6 @@ import {buildQueryString} from '@utils/helpers';
import {PER_PAGE_DEFAULT} from './constants';
import type ClientBase from './base';
export interface ClientTeamsMix {
createTeam: (team: Team) => Promise<Team>;
deleteTeam: (teamId: string) => Promise<any>;
@@ -30,9 +28,9 @@ export interface ClientTeamsMix {
getTeamIconUrl: (teamId: string, lastTeamIconUpdate: number) => string;
}
const ClientTeams = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
const ClientTeams = (superclass: any) => class extends superclass {
createTeam = async (team: Team) => {
this.analytics?.trackAPI('api_teams_create');
this.analytics.trackAPI('api_teams_create');
return this.doFetch(
`${this.getTeamsRoute()}`,
@@ -41,7 +39,7 @@ const ClientTeams = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
deleteTeam = async (teamId: string) => {
this.analytics?.trackAPI('api_teams_delete');
this.analytics.trackAPI('api_teams_delete');
return this.doFetch(
`${this.getTeamRoute(teamId)}`,
@@ -50,7 +48,7 @@ const ClientTeams = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
updateTeam = async (team: Team) => {
this.analytics?.trackAPI('api_teams_update_name', {team_id: team.id});
this.analytics.trackAPI('api_teams_update_name', {team_id: team.id});
return this.doFetch(
`${this.getTeamRoute(team.id)}`,
@@ -59,7 +57,7 @@ const ClientTeams = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
patchTeam = async (team: Partial<Team> & {id: string}) => {
this.analytics?.trackAPI('api_teams_patch_name', {team_id: team.id});
this.analytics.trackAPI('api_teams_patch_name', {team_id: team.id});
return this.doFetch(
`${this.getTeamRoute(team.id)}/patch`,
@@ -82,7 +80,7 @@ const ClientTeams = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
getTeamByName = async (teamName: string) => {
this.analytics?.trackAPI('api_teams_get_team_by_name');
this.analytics.trackAPI('api_teams_get_team_by_name');
return this.doFetch(
this.getTeamNameRoute(teamName),
@@ -133,7 +131,7 @@ const ClientTeams = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
addToTeam = async (teamId: string, userId: string) => {
this.analytics?.trackAPI('api_teams_invite_members', {team_id: teamId});
this.analytics.trackAPI('api_teams_invite_members', {team_id: teamId});
const member = {user_id: userId, team_id: teamId};
return this.doFetch(
@@ -143,7 +141,7 @@ const ClientTeams = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
addUsersToTeamGracefully = (teamId: string, userIds: string[]) => {
this.analytics?.trackAPI('api_teams_batch_add_members', {team_id: teamId, count: userIds.length});
this.analytics.trackAPI('api_teams_batch_add_members', {team_id: teamId, count: userIds.length});
const members: Array<{team_id: string; user_id: string}> = [];
userIds.forEach((id) => members.push({team_id: teamId, user_id: id}));
@@ -155,7 +153,7 @@ const ClientTeams = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
sendEmailInvitesToTeamGracefully = (teamId: string, emails: string[]) => {
this.analytics?.trackAPI('api_teams_invite_members', {team_id: teamId});
this.analytics.trackAPI('api_teams_invite_members', {team_id: teamId});
return this.doFetch(
`${this.getTeamRoute(teamId)}/invite/email?graceful=true`,
@@ -172,7 +170,7 @@ const ClientTeams = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
removeFromTeam = async (teamId: string, userId: string) => {
this.analytics?.trackAPI('api_teams_remove_members', {team_id: teamId});
this.analytics.trackAPI('api_teams_remove_members', {team_id: teamId});
return this.doFetch(
`${this.getTeamMemberRoute(teamId, userId)}`,

View File

@@ -5,8 +5,6 @@ import {buildQueryString, isMinimumServerVersion} from '@utils/helpers';
import {PER_PAGE_DEFAULT} from './constants';
import type ClientBase from './base';
export interface ClientThreadsMix {
getThreads: (userId: string, teamId: string, before?: string, after?: string, pageSize?: number, deleted?: boolean, unread?: boolean, since?: number, totalsOnly?: boolean, serverVersion?: string) => Promise<GetUserThreadsResponse>;
getThread: (userId: string, teamId: string, threadId: string, extended?: boolean) => Promise<any>;
@@ -16,7 +14,7 @@ export interface ClientThreadsMix {
updateThreadFollow: (userId: string, teamId: string, threadId: string, state: boolean) => Promise<any>;
}
const ClientThreads = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
const ClientThreads = (superclass: any) => class extends superclass {
getThreads = async (userId: string, teamId: string, before = '', after = '', pageSize = PER_PAGE_DEFAULT, deleted = false, unread = false, since = 0, totalsOnly = false, serverVersion = '') => {
const queryStringObj: Record<string, any> = {
extended: 'true',

View File

@@ -1,14 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type ClientBase from './base';
export interface ClientTosMix {
updateMyTermsOfServiceStatus: (termsOfServiceId: string, accepted: boolean) => Promise<{status: string}>;
getTermsOfService: () => Promise<TermsOfService>;
}
const ClientTos = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
const ClientTos = (superclass: any) => class extends superclass {
updateMyTermsOfServiceStatus = async (termsOfServiceId: string, accepted: boolean) => {
return this.doFetch(
`${this.getUserRoute('me')}/terms_of_service`,

View File

@@ -6,8 +6,6 @@ import {buildQueryString} from '@utils/helpers';
import {PER_PAGE_DEFAULT} from './constants';
import type ClientBase from './base';
export interface ClientUsersMix {
createUser: (user: UserProfile, token: string, inviteId: string) => Promise<UserProfile>;
patchMe: (userPatch: Partial<UserProfile>) => Promise<UserProfile>;
@@ -26,7 +24,7 @@ export interface ClientUsersMix {
getProfilesInTeam: (teamId: string, page?: number, perPage?: number, sort?: string, options?: Record<string, any>) => Promise<UserProfile[]>;
getProfilesNotInTeam: (teamId: string, groupConstrained: boolean, page?: number, perPage?: number) => Promise<UserProfile[]>;
getProfilesWithoutTeam: (page?: number, perPage?: number, options?: Record<string, any>) => Promise<UserProfile[]>;
getProfilesInChannel: (channelId: string, options?: GetUsersOptions) => Promise<UserProfile[]>;
getProfilesInChannel: (channelId: string, page?: number, perPage?: number, sort?: string) => Promise<UserProfile[]>;
getProfilesInGroupChannels: (channelsIds: string[]) => Promise<{[x: string]: UserProfile[]}>;
getProfilesNotInChannel: (teamId: string, channelId: string, groupConstrained: boolean, page?: number, perPage?: number) => Promise<UserProfile[]>;
getMe: () => Promise<UserProfile>;
@@ -39,7 +37,7 @@ export interface ClientUsersMix {
getSessions: (userId: string) => Promise<Session[]>;
checkUserMfa: (loginId: string) => Promise<{mfa_required: boolean}>;
attachDevice: (deviceId: string) => Promise<any>;
searchUsers: (term: string, options: SearchUserOptions) => Promise<UserProfile[]>;
searchUsers: (term: string, options: any) => Promise<UserProfile[]>;
getStatusesByIds: (userIds: string[]) => Promise<UserStatus[]>;
getStatus: (userId: string) => Promise<UserStatus>;
updateStatus: (status: UserStatus) => Promise<UserStatus>;
@@ -48,9 +46,9 @@ export interface ClientUsersMix {
removeRecentCustomStatus: (customStatus: UserCustomStatus) => Promise<{status: string}>;
}
const ClientUsers = <TBase extends Constructor<ClientBase>>(superclass: TBase) => class extends superclass {
const ClientUsers = (superclass: any) => class extends superclass {
createUser = async (user: UserProfile, token: string, inviteId: string) => {
this.analytics?.trackAPI('api_users_create');
this.analytics.trackAPI('api_users_create');
const queryParams: any = {};
@@ -76,7 +74,7 @@ const ClientUsers = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
patchUser = async (userPatch: Partial<UserProfile> & {id: string}) => {
this.analytics?.trackAPI('api_users_patch');
this.analytics.trackAPI('api_users_patch');
return this.doFetch(
`${this.getUserRoute(userPatch.id)}/patch`,
@@ -85,7 +83,7 @@ const ClientUsers = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
updateUser = async (user: UserProfile) => {
this.analytics?.trackAPI('api_users_update');
this.analytics.trackAPI('api_users_update');
return this.doFetch(
`${this.getUserRoute(user.id)}`,
@@ -94,7 +92,7 @@ const ClientUsers = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
demoteUserToGuest = async (userId: string) => {
this.analytics?.trackAPI('api_users_demote_user_to_guest');
this.analytics.trackAPI('api_users_demote_user_to_guest');
return this.doFetch(
`${this.getUserRoute(userId)}/demote`,
@@ -103,7 +101,7 @@ const ClientUsers = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
getKnownUsers = async () => {
this.analytics?.trackAPI('api_get_known_users');
this.analytics.trackAPI('api_get_known_users');
return this.doFetch(
`${this.getUsersRoute()}/known`,
@@ -112,7 +110,7 @@ const ClientUsers = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
sendPasswordResetEmail = async (email: string) => {
this.analytics?.trackAPI('api_users_send_password_reset');
this.analytics.trackAPI('api_users_send_password_reset');
return this.doFetch(
`${this.getUsersRoute()}/password/reset/send`,
@@ -121,7 +119,7 @@ const ClientUsers = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
setDefaultProfileImage = async (userId: string) => {
this.analytics?.trackAPI('api_users_set_default_profile_picture');
this.analytics.trackAPI('api_users_set_default_profile_picture');
return this.doFetch(
`${this.getUserRoute(userId)}/image`,
@@ -130,10 +128,10 @@ const ClientUsers = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
login = async (loginId: string, password: string, token = '', deviceId = '', ldapOnly = false) => {
this.analytics?.trackAPI('api_users_login');
this.analytics.trackAPI('api_users_login');
if (ldapOnly) {
this.analytics?.trackAPI('api_users_login_ldap');
this.analytics.trackAPI('api_users_login_ldap');
}
const body: any = {
@@ -161,7 +159,7 @@ const ClientUsers = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
loginById = async (id: string, password: string, token = '', deviceId = '') => {
this.analytics?.trackAPI('api_users_login');
this.analytics.trackAPI('api_users_login');
const body: any = {
device_id: deviceId,
id,
@@ -183,7 +181,7 @@ const ClientUsers = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
logout = async () => {
this.analytics?.trackAPI('api_users_logout');
this.analytics.trackAPI('api_users_logout');
const response = await this.doFetch(
`${this.getUsersRoute()}/logout`,
@@ -194,7 +192,7 @@ const ClientUsers = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
getProfiles = async (page = 0, perPage = PER_PAGE_DEFAULT, options = {}) => {
this.analytics?.trackAPI('api_profiles_get');
this.analytics.trackAPI('api_profiles_get');
return this.doFetch(
`${this.getUsersRoute()}${buildQueryString({page, per_page: perPage, ...options})}`,
@@ -203,7 +201,7 @@ const ClientUsers = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
getProfilesByIds = async (userIds: string[], options = {}) => {
this.analytics?.trackAPI('api_profiles_get_by_ids');
this.analytics.trackAPI('api_profiles_get_by_ids');
return this.doFetch(
`${this.getUsersRoute()}/ids${buildQueryString(options)}`,
@@ -212,7 +210,7 @@ const ClientUsers = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
getProfilesByUsernames = async (usernames: string[]) => {
this.analytics?.trackAPI('api_profiles_get_by_usernames');
this.analytics.trackAPI('api_profiles_get_by_usernames');
return this.doFetch(
`${this.getUsersRoute()}/usernames`,
@@ -221,7 +219,7 @@ const ClientUsers = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
getProfilesInTeam = async (teamId: string, page = 0, perPage = PER_PAGE_DEFAULT, sort = '', options = {}) => {
this.analytics?.trackAPI('api_profiles_get_in_team', {team_id: teamId, sort});
this.analytics.trackAPI('api_profiles_get_in_team', {team_id: teamId, sort});
return this.doFetch(
`${this.getUsersRoute()}${buildQueryString({...options, in_team: teamId, page, per_page: perPage, sort})}`,
@@ -230,7 +228,7 @@ const ClientUsers = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
getProfilesNotInTeam = async (teamId: string, groupConstrained: boolean, page = 0, perPage = PER_PAGE_DEFAULT) => {
this.analytics?.trackAPI('api_profiles_get_not_in_team', {team_id: teamId, group_constrained: groupConstrained});
this.analytics.trackAPI('api_profiles_get_not_in_team', {team_id: teamId, group_constrained: groupConstrained});
const queryStringObj: any = {not_in_team: teamId, page, per_page: perPage};
if (groupConstrained) {
@@ -244,7 +242,7 @@ const ClientUsers = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
getProfilesWithoutTeam = async (page = 0, perPage = PER_PAGE_DEFAULT, options = {}) => {
this.analytics?.trackAPI('api_profiles_get_without_team');
this.analytics.trackAPI('api_profiles_get_without_team');
return this.doFetch(
`${this.getUsersRoute()}${buildQueryString({...options, without_team: 1, page, per_page: perPage})}`,
@@ -252,10 +250,10 @@ const ClientUsers = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
);
};
getProfilesInChannel = async (channelId: string, options: GetUsersOptions) => {
this.analytics?.trackAPI('api_profiles_get_in_channel', {channel_id: channelId});
getProfilesInChannel = async (channelId: string, page = 0, perPage = PER_PAGE_DEFAULT, sort = '') => {
this.analytics.trackAPI('api_profiles_get_in_channel', {channel_id: channelId});
const queryStringObj = {in_channel: channelId, ...options};
const queryStringObj = {in_channel: channelId, page, per_page: perPage, sort};
return this.doFetch(
`${this.getUsersRoute()}${buildQueryString(queryStringObj)}`,
{method: 'get'},
@@ -263,7 +261,7 @@ const ClientUsers = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
getProfilesInGroupChannels = async (channelsIds: string[]) => {
this.analytics?.trackAPI('api_profiles_get_in_group_channels', {channelsIds});
this.analytics.trackAPI('api_profiles_get_in_group_channels', {channelsIds});
return this.doFetch(
`${this.getUsersRoute()}/group_channels`,
@@ -272,7 +270,7 @@ const ClientUsers = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
getProfilesNotInChannel = async (teamId: string, channelId: string, groupConstrained: boolean, page = 0, perPage = PER_PAGE_DEFAULT) => {
this.analytics?.trackAPI('api_profiles_get_not_in_channel', {team_id: teamId, channel_id: channelId, group_constrained: groupConstrained});
this.analytics.trackAPI('api_profiles_get_not_in_channel', {team_id: teamId, channel_id: channelId, group_constrained: groupConstrained});
const queryStringObj: any = {in_team: teamId, not_in_channel: channelId, page, per_page: perPage};
if (groupConstrained) {
@@ -374,7 +372,7 @@ const ClientUsers = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
};
searchUsers = async (term: string, options: any) => {
this.analytics?.trackAPI('api_search_users');
this.analytics.trackAPI('api_search_users');
return this.doFetch(
`${this.getUsersRoute()}/search`,

View File

@@ -8,7 +8,7 @@ import {switchMap} from 'rxjs/operators';
import {Preferences} from '@constants';
import {queryAllCustomEmojis} from '@queries/servers/custom_emoji';
import {queryEmojiPreferences} from '@queries/servers/preference';
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
import {observeConfigBooleanValue} from '@queries/servers/system';
import EmojiSuggestion from './emoji_suggestion';
@@ -21,9 +21,12 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
const isCustomEmojisEnabled = observeConfigBooleanValue(database, 'EnableCustomEmoji');
return {
customEmojis: isCustomEmojisEnabled.pipe(
switchMap((enabled) => (enabled ? queryAllCustomEmojis(database).observe() : of$(emptyEmojiList))),
switchMap((enabled) => (enabled ?
queryAllCustomEmojis(database).observe() :
of$(emptyEmojiList)),
),
),
skinTone: queryEmojiPreferences(database, Preferences.EMOJI_SKINTONE).
skinTone: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_EMOJI, Preferences.EMOJI_SKINTONE).
observeWithColumns(['value']).pipe(
switchMap((prefs) => of$(prefs?.[0]?.value ?? 'default')),
),

View File

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
import React from 'react';
import {StyleProp, TextStyle, View, ViewStyle} from 'react-native';
import {StyleProp, TextStyle, View, ViewProps, ViewStyle} from 'react-native';
import FormattedText from '@components/formatted_text';
import {useTheme} from '@context/theme';
@@ -36,7 +36,7 @@ export type SectionText = {
values?: MessageDescriptor;
}
export type BlockProps = {
export type BlockProps = ViewProps & {
children: React.ReactNode;
disableFooter?: boolean;
disableHeader?: boolean;
@@ -56,12 +56,16 @@ const Block = ({
headerStyles,
headerText,
footerStyles,
...props
}: BlockProps) => {
const theme = useTheme();
const styles = getStyleSheet(theme);
return (
<View style={styles.container}>
<View
style={styles.container}
{...props}
>
{(headerText && !disableHeader) &&
<FormattedText
defaultMessage={headerText.defaultMessage}

View File

@@ -1,80 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback} from 'react';
import {defineMessages, useIntl} from 'react-intl';
import CompassIcon from '@components/compass_icon';
import OptionBox from '@components/option_box';
import {Screens} from '@constants';
import {useTheme} from '@context/theme';
import {t} from '@i18n';
import {goToScreen, showModal} from '@screens/navigation';
import {changeOpacity} from '@utils/theme';
import type {StyleProp, ViewStyle} from 'react-native';
type Props = {
channelId: string;
containerStyle?: StyleProp<ViewStyle>;
displayName: string;
inModal?: boolean;
testID?: string;
}
const messages = defineMessages({
title: {
id: t('mobile.channel_add_people.title'),
defaultMessage: 'Add Members',
},
boxText: {
id: t('intro.add_people'),
defaultMessage: 'Add People',
},
});
const {CHANNEL_ADD_PEOPLE} = Screens;
const closeButtonId = 'close-add-people';
const AddPeopleBox = ({channelId, containerStyle, displayName, inModal, testID}: Props) => {
const {formatMessage} = useIntl();
const theme = useTheme();
const onAddPeople = useCallback(async () => {
const closeButton = await CompassIcon.getImageSourceSync('close', 24, theme.sidebarHeaderTextColor);
const title = formatMessage(messages.title);
const options = {
topBar: {
subtitle: {
color: changeOpacity(theme.sidebarHeaderTextColor, 0.72),
text: displayName,
},
leftButtons: inModal ? [] : [{
id: closeButtonId,
icon: closeButton,
testID: 'close.channel_info.button',
}],
},
};
if (inModal) {
goToScreen(CHANNEL_ADD_PEOPLE, title, {channelId}, options);
return;
}
showModal(CHANNEL_ADD_PEOPLE, title, {channelId, closeButtonId}, options);
}, [formatMessage, channelId, inModal]);
return (
<OptionBox
containerStyle={containerStyle}
iconName='account-plus-outline'
onPress={onAddPeople}
testID={testID}
text={formatMessage(messages.boxText)}
/>
);
};
export default AddPeopleBox;

View File

@@ -1,28 +1,44 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import React, {useCallback} from 'react';
import {useIntl} from 'react-intl';
import {observeChannel} from '@queries/servers/channel';
import OptionBox from '@components/option_box';
import {Screens} from '@constants';
import {dismissBottomSheet, goToScreen, showModal} from '@screens/navigation';
import AddPeopleBox from './add_people_box';
import type {StyleProp, ViewStyle} from 'react-native';
import type {WithDatabaseArgs} from '@typings/database/database';
type Props = WithDatabaseArgs & {
type Props = {
channelId: string;
containerStyle?: StyleProp<ViewStyle>;
inModal?: boolean;
testID?: string;
}
const enhanced = withObservables(['channelId'], ({channelId, database}: Props) => {
const channel = observeChannel(database, channelId);
const displayName = channel.pipe(switchMap((c) => of$(c?.displayName)));
const AddPeopleBox = ({channelId, containerStyle, inModal, testID}: Props) => {
const intl = useIntl();
return {
displayName,
};
});
const onAddPeople = useCallback(async () => {
const title = intl.formatMessage({id: 'intro.add_people', defaultMessage: 'Add People'});
if (inModal) {
goToScreen(Screens.CHANNEL_ADD_PEOPLE, title, {channelId});
return;
}
await dismissBottomSheet();
showModal(Screens.CHANNEL_ADD_PEOPLE, title, {channelId});
}, [intl, channelId, inModal]);
export default withDatabase(enhanced(AddPeopleBox));
return (
<OptionBox
containerStyle={containerStyle}
iconName='account-plus-outline'
onPress={onAddPeople}
testID={testID}
text={intl.formatMessage({id: 'intro.add_people', defaultMessage: 'Add People'})}
/>
);
};
export default AddPeopleBox;

View File

@@ -5,7 +5,6 @@ import React, {useCallback} from 'react';
import {StyleSheet, View} from 'react-native';
import ChannelInfoStartButton from '@calls/components/channel_info_start';
import AddPeopleBox from '@components/channel_actions/add_people_box';
import CopyChannelLinkBox from '@components/channel_actions/copy_channel_link_box';
import FavoriteBox from '@components/channel_actions/favorite_box';
import MutedBox from '@components/channel_actions/mute_box';
@@ -14,6 +13,8 @@ import {useServerUrl} from '@context/server';
import {dismissBottomSheet} from '@screens/navigation';
import {isTypeDMorGM} from '@utils/channel';
// import AddPeopleBox from '@components/channel_actions/add_people_box';
type Props = {
channelId: string;
channelType?: ChannelType;
@@ -69,6 +70,7 @@ const ChannelActions = ({channelId, channelType, inModal = false, dismissChannel
testID={`${testID}.set_header.action`}
/>
}
{/* Add back in after MM-47655 is resolved. https://mattermost.atlassian.net/browse/MM-47655
{!isDM &&
<AddPeopleBox
channelId={channelId}
@@ -76,6 +78,7 @@ const ChannelActions = ({channelId, channelType, inModal = false, dismissChannel
testID={`${testID}.add_people.action`}
/>
}
*/}
{!isDM && !callsEnabled &&
<>
<View style={styles.separator}/>

View File

@@ -23,7 +23,7 @@ type Props = {
testID?: string;
}
const LeaveChannelLabel = ({canLeave, channelId, displayName, isOptionItem, type, testID}: Props) => {
const LeaveChanelLabel = ({canLeave, channelId, displayName, isOptionItem, type, testID}: Props) => {
const intl = useIntl();
const serverUrl = useServerUrl();
const isTablet = useIsTablet();
@@ -183,4 +183,4 @@ const LeaveChannelLabel = ({canLeave, channelId, displayName, isOptionItem, type
);
};
export default LeaveChannelLabel;
export default LeaveChanelLabel;

View File

@@ -1,33 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {queryUsersById} from '@queries/servers/user';
import ManageMembersLabel from './manage_members_label';
import type {WithDatabaseArgs} from '@typings/database/database';
type OwnProps = WithDatabaseArgs & {
isDefaultChannel: boolean;
userId: string;
}
const enhanced = withObservables(['isDefaultChannel', 'userId'], ({isDefaultChannel, userId, database}: OwnProps) => {
const users = queryUsersById(database, [userId]).observe();
const canRemoveUser = users.pipe(
switchMap((u) => {
return of$(!isDefaultChannel || (isDefaultChannel && u[0].isGuest));
}),
);
return {
canRemoveUser,
};
});
export default withDatabase(enhanced(ManageMembersLabel));

View File

@@ -1,152 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback} from 'react';
import {defineMessages, useIntl} from 'react-intl';
import {Alert, DeviceEventEmitter} from 'react-native';
import {fetchChannelStats, removeMemberFromChannel, updateChannelMemberSchemeRoles} from '@actions/remote/channel';
import OptionItem from '@components/option_item';
import {Events, Members} from '@constants';
import {useServerUrl} from '@context/server';
import {t} from '@i18n';
import {dismissBottomSheet} from '@screens/navigation';
import {alertErrorWithFallback} from '@utils/draft';
import type {ManageOptionsTypes} from '@constants/members';
const {MAKE_CHANNEL_ADMIN, MAKE_CHANNEL_MEMBER, REMOVE_USER} = Members.ManageOptions;
const messages = defineMessages({
role_change_error: {
id: t('mobile.manage_members.change_role.error'),
defaultMessage: 'An error occurred while trying to update the role. Please check your connection and try again.',
},
make_channel_admin: {
id: t('mobile.manage_members.make_channel_admin'),
defaultMessage: 'Make Channel Admin',
},
make_channel_member: {
id: t('mobile.manage_members.make_channel_member'),
defaultMessage: 'Make Channel Member',
},
remove_title: {
id: t('mobile.manage_members.remove_member'),
defaultMessage: 'Remove From Channel',
},
remove_message: {
id: t('mobile.manage_members.message'),
defaultMessage: 'Are you sure you want to remove the selected member from the channel?',
},
remove_cancel: {
id: t('mobile.manage_members.cancel'),
defaultMessage: 'Cancel',
},
remove_confirm: {
id: t('mobile.manage_members.remove'),
defaultMessage: 'Remove',
},
});
type Props = {
canRemoveUser: boolean;
channelId: string;
manageOption: ManageOptionsTypes;
testID?: string;
userId: string;
}
const ManageMembersLabel = ({canRemoveUser, channelId, manageOption, testID, userId}: Props) => {
const intl = useIntl();
const {formatMessage} = intl;
const serverUrl = useServerUrl();
const handleRemoveUser = useCallback(async () => {
removeMemberFromChannel(serverUrl, channelId, userId);
fetchChannelStats(serverUrl, channelId, false);
await dismissBottomSheet();
DeviceEventEmitter.emit(Events.REMOVE_USER_FROM_CHANNEL, userId);
}, [channelId, serverUrl, userId]);
const removeFromChannel = useCallback(() => {
Alert.alert(
formatMessage(messages.remove_title),
formatMessage(messages.remove_message),
[{
text: formatMessage(messages.remove_cancel),
style: 'cancel',
}, {
text: formatMessage(messages.remove_confirm),
style: 'destructive',
onPress: handleRemoveUser,
}], {cancelable: false},
);
}, [formatMessage, handleRemoveUser]);
const updateChannelMemberSchemeRole = useCallback(async (schemeAdmin: boolean) => {
const result = await updateChannelMemberSchemeRoles(serverUrl, channelId, userId, true, schemeAdmin);
if (result.error) {
alertErrorWithFallback(intl, result.error, messages.role_change_error);
}
await dismissBottomSheet();
DeviceEventEmitter.emit(Events.MANAGE_USER_CHANGE_ROLE, {userId, schemeAdmin});
}, [channelId, userId, intl, serverUrl]);
const onAction = useCallback(() => {
switch (manageOption) {
case REMOVE_USER:
removeFromChannel();
break;
case MAKE_CHANNEL_ADMIN:
updateChannelMemberSchemeRole(true);
break;
case MAKE_CHANNEL_MEMBER:
updateChannelMemberSchemeRole(false);
break;
default:
break;
}
}, [manageOption, removeFromChannel, updateChannelMemberSchemeRole]);
let actionText;
let icon;
let isDestructive = false;
switch (manageOption) {
case REMOVE_USER:
actionText = (formatMessage(messages.remove_title));
icon = 'trash-can-outline';
isDestructive = true;
break;
case MAKE_CHANNEL_ADMIN:
actionText = formatMessage(messages.make_channel_admin);
icon = 'account-outline';
break;
case MAKE_CHANNEL_MEMBER:
actionText = formatMessage(messages.make_channel_member);
icon = 'account-outline';
break;
default:
break;
}
if (manageOption === REMOVE_USER && !canRemoveUser) {
return null;
}
if (!actionText) {
return null;
}
return (
<OptionItem
action={onAction}
destructive={isDestructive}
icon={icon}
label={actionText}
testID={testID}
type='default'
/>
);
};
export default ManageMembersLabel;

View File

@@ -10,7 +10,7 @@ import {switchMap, distinctUntilChanged} from 'rxjs/operators';
import {observeChannelsWithCalls} from '@calls/state';
import {General} from '@constants';
import {withServerUrl} from '@context/server';
import {observeChannelSettings, observeMyChannel, queryChannelMembers} from '@queries/servers/channel';
import {observeIsMutedSetting, observeMyChannel, queryChannelMembers} from '@queries/servers/channel';
import {queryDraft} from '@queries/servers/drafts';
import {observeCurrentChannelId, observeCurrentUserId} from '@queries/servers/system';
import {observeTeam} from '@queries/servers/team';
@@ -19,7 +19,6 @@ import ChannelItem from './channel_item';
import type {WithDatabaseArgs} from '@typings/database/database';
import type ChannelModel from '@typings/database/models/servers/channel';
import type MyChannelModel from '@typings/database/models/servers/my_channel';
type EnhanceProps = WithDatabaseArgs & {
channel: ChannelModel;
@@ -27,8 +26,6 @@ type EnhanceProps = WithDatabaseArgs & {
serverUrl?: string;
}
const observeIsMutedSetting = (mc: MyChannelModel) => observeChannelSettings(mc.database, mc.id).pipe(switchMap((s) => of$(s?.notifyProps?.mark_unread === General.MENTION)));
const enhance = withObservables(['channel', 'showTeamName'], ({
channel,
database,
@@ -53,7 +50,7 @@ const enhance = withObservables(['channel', 'showTeamName'], ({
if (!mc) {
return of$(false);
}
return observeIsMutedSetting(mc);
return observeIsMutedSetting(database, mc.id);
}),
);

View File

@@ -12,8 +12,9 @@ import {switchMap} from 'rxjs/operators';
import FormattedDate from '@components/formatted_date';
import FormattedText from '@components/formatted_text';
import FormattedTime from '@components/formatted_time';
import {getDisplayNamePreferenceAsBool} from '@helpers/api/preference';
import {queryDisplayNamePreferences} from '@queries/servers/preference';
import {Preferences} from '@constants';
import {getPreferenceAsBool} from '@helpers/api/preference';
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
import {observeCurrentUser} from '@queries/servers/user';
import {getCurrentMomentForTimezone} from '@utils/helpers';
import {makeStyleSheetFromTheme} from '@utils/theme';
@@ -133,10 +134,10 @@ const CustomStatusExpiry = ({currentUser, isMilitaryTime, showPrefix, showTimeCo
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => ({
currentUser: observeCurrentUser(database),
isMilitaryTime: queryDisplayNamePreferences(database).
isMilitaryTime: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS).
observeWithColumns(['value']).pipe(
switchMap(
(preferences) => of$(getDisplayNamePreferenceAsBool(preferences, 'use_military_time')),
(preferences) => of$(getPreferenceAsBool(preferences, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', false)),
),
),
}));

View File

@@ -1,74 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {defineMessages, useIntl} from 'react-intl';
import {Text, View} from 'react-native';
import {popToRoot} from '@app/screens/navigation';
import {useTheme} from '@context/theme';
import {t} from '@i18n';
import Button from '@screens/bottom_sheet/button';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
const messages = defineMessages({
no_more_members_title: {
id: t('mobile.no_more_members.title'),
defaultMessage: 'No other members to add',
},
no_more_members_subtext: {
id: t('mobile.no_more_members.subtext'),
defaultMessage: 'All team members are already in this channel.',
},
go_back: {
id: t('mobile.no_more_members.go_back'),
defaultMessage: 'Go back',
},
});
const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => {
return {
container: {
alignItems: 'center' as const,
flexGrow: 1,
height: '100%',
justifyContent: 'center' as const,
},
buttonContainer: {
marginTop: 24,
},
title: {
color: theme.centerChannelColor,
...typography('Heading', 400, 'SemiBold'),
},
subText: {
color: changeOpacity(theme.centerChannelColor, 0.72),
marginTop: 8,
...typography('Body', 200),
},
};
});
const NoResultsWithButton = () => {
const theme = useTheme();
const styles = getStyleFromTheme(theme);
const {formatMessage} = useIntl();
return (
<View style={styles.container}>
<Text style={styles.title}>{formatMessage(messages.no_more_members_title)}</Text>
<Text style={styles.subText}>{formatMessage(messages.no_more_members_subtext)}</Text>
<View style={styles.buttonContainer}>
<Button
onPress={popToRoot}
icon={'arrow-left'}
text={formatMessage(messages.go_back)}
/>
</View>
</View>
);
};
export default NoResultsWithButton;

View File

@@ -6,8 +6,8 @@ import withObservables from '@nozbe/with-observables';
import {of as of$, combineLatest} from 'rxjs';
import {Preferences} from '@constants';
import {getDisplayNamePreferenceAsBool} from '@helpers/api/preference';
import {queryDisplayNamePreferences} from '@queries/servers/preference';
import {getPreferenceAsBool} from '@helpers/api/preference';
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
import {observeConfigBooleanValue} from '@queries/servers/system';
import Opengraph from './opengraph';
@@ -22,10 +22,10 @@ const enhance = withObservables(
}
const linkPreviewsConfig = observeConfigBooleanValue(database, 'EnableLinkPreviews');
const linkPreviewPreference = queryDisplayNamePreferences(database, Preferences.LINK_PREVIEW_DISPLAY).
const linkPreviewPreference = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.LINK_PREVIEW_DISPLAY).
observeWithColumns(['value']);
const showLinkPreviews = combineLatest([linkPreviewsConfig, linkPreviewPreference], (cfg, pref) => {
const previewsEnabled = getDisplayNamePreferenceAsBool(pref, Preferences.LINK_PREVIEW_DISPLAY, true);
const previewsEnabled = getPreferenceAsBool(pref, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.LINK_PREVIEW_DISPLAY, true);
return of$(previewsEnabled && cfg);
});

View File

@@ -6,9 +6,10 @@ import withObservables from '@nozbe/with-observables';
import {of as of$} from 'rxjs';
import {map, switchMap} from 'rxjs/operators';
import {getDisplayNamePreferenceAsBool} from '@helpers/api/preference';
import {Preferences} from '@constants';
import {getPreferenceAsBool} from '@helpers/api/preference';
import {observePost, observePostAuthor, queryPostReplies} from '@queries/servers/post';
import {queryDisplayNamePreferences} from '@queries/servers/preference';
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
import {observeConfigBooleanValue} from '@queries/servers/system';
import {observeTeammateNameDisplay, observeUser} from '@queries/servers/user';
@@ -25,12 +26,12 @@ type HeaderInputProps = {
const withHeaderProps = withObservables(
['post', 'differentThreadSequence'],
({post, database, differentThreadSequence}: WithDatabaseArgs & HeaderInputProps) => {
const preferences = queryDisplayNamePreferences(database).
const preferences = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS).
observeWithColumns(['value']);
const author = observePostAuthor(database, post);
const enablePostUsernameOverride = observeConfigBooleanValue(database, 'EnablePostUsernameOverride');
const isTimezoneEnabled = observeConfigBooleanValue(database, 'ExperimentalTimezone');
const isMilitaryTime = preferences.pipe(map((prefs) => getDisplayNamePreferenceAsBool(prefs, 'use_military_time')));
const isMilitaryTime = preferences.pipe(map((prefs) => getPreferenceAsBool(prefs, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', false)));
const teammateNameDisplay = observeTeammateNameDisplay(database);
const commentCount = queryPostReplies(database, post.rootId || post.id).observeCount();
const isCustomStatusEnabled = observeConfigBooleanValue(database, 'EnableCustomUserStatuses');

View File

@@ -102,7 +102,7 @@ const withPost = withObservables(
const isEphemeral = of$(isPostEphemeral(post));
if (post.props?.add_channel_member && isPostEphemeral(post)) {
isPostAddChannelMember = observeCanManageChannelMembers(database, post.channelId, currentUser);
isPostAddChannelMember = observeCanManageChannelMembers(database, post, currentUser);
}
let highlightReplyBar = of$(false);

View File

@@ -6,8 +6,9 @@ import withObservables from '@nozbe/with-observables';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {Preferences} from '@constants';
import {observePost, queryPostReplies} from '@queries/servers/post';
import {querySavedPostsPreferences} from '@queries/servers/preference';
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
import ThreadOverview from './thread_overview';
@@ -18,7 +19,7 @@ const enhanced = withObservables(
({database, rootId}: WithDatabaseArgs & {rootId: string}) => {
return {
rootPost: observePost(database, rootId),
isSaved: querySavedPostsPreferences(database, rootId).
isSaved: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_SAVED_POST, rootId).
observeWithColumns(['value']).
pipe(
switchMap((pref) => of$(Boolean(pref[0]?.value === 'true'))),

View File

@@ -1,68 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// See LICENSE.txt for license information.
import React from 'react';
import {GestureResponderEvent, StyleSheet, Text, View} from 'react-native';
import CompassIcon from '@components/compass_icon';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {useTheme} from '@context/theme';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import {changeOpacity} from '@utils/theme';
type Props = {
disabled?: boolean;
onPress?: (e: GestureResponderEvent) => void;
icon?: string;
testID?: string;
text?: string;
}
const styles = StyleSheet.create({
button: {
display: 'flex',
flexDirection: 'row',
},
icon_container: {
width: 24,
height: 24,
top: -1,
marginRight: 4,
},
});
export default function Button({disabled = false, onPress, icon, testID, text}: Props) {
const theme = useTheme();
const buttonType = disabled ? 'disabled' : 'default';
const styleButtonText = buttonTextStyle(theme, 'lg', 'primary', buttonType);
const styleButtonBackground = buttonBackgroundStyle(theme, 'lg', 'primary', buttonType);
const iconColor = disabled ? changeOpacity(theme.centerChannelColor, 0.32) : theme.buttonColor;
return (
<TouchableWithFeedback
onPress={onPress}
type='opacity'
style={[styles.button, styleButtonBackground]}
testID={testID}
>
{icon && (
<View style={styles.icon_container}>
<CompassIcon
size={24}
name={icon}
color={iconColor}
/>
</View>
)}
{text && (
<Text
style={styleButtonText}
>{text}</Text>
)}
</TouchableWithFeedback>
);
}

View File

@@ -11,9 +11,9 @@ import Toast from '@components/toast';
import {General} from '@constants';
import {useTheme} from '@context/theme';
import {useIsTablet, useKeyboardHeightWithDuration} from '@hooks/device';
import Button from '@screens/bottom_sheet/button';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import Button from './button';
import SelectedUser from './selected_user';
type Props = {

View File

@@ -9,8 +9,9 @@ import {map} from 'rxjs/operators';
import FormattedText from '@components/formatted_text';
import FormattedTime from '@components/formatted_time';
import {getDisplayNamePreferenceAsBool} from '@helpers/api/preference';
import {queryDisplayNamePreferences} from '@queries/servers/preference';
import {Preferences} from '@constants';
import {getPreferenceAsBool} from '@helpers/api/preference';
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
import {observeConfigBooleanValue} from '@queries/servers/system';
import {observeCurrentUser} from '@queries/servers/user';
import {makeStyleSheetFromTheme} from '@utils/theme';
@@ -79,11 +80,11 @@ const SystemHeader = ({isMilitaryTime, isTimezoneEnabled, createAt, theme, user}
};
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
const preferences = queryDisplayNamePreferences(database, 'use_military_time').
const preferences = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time').
observeWithColumns(['value']);
const isTimezoneEnabled = observeConfigBooleanValue(database, 'ExperimentalTimezone');
const isMilitaryTime = preferences.pipe(
map((prefs) => getDisplayNamePreferenceAsBool(prefs, 'use_military_time')),
map((prefs) => getPreferenceAsBool(prefs, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', false)),
);
const user = observeCurrentUser(database);

View File

@@ -21,7 +21,7 @@ const withTeams = withObservables([], ({database}: WithDatabaseArgs) => {
const teamIds = queryJoinedTeams(database).observe().pipe(
map((ts) => ts.map((t) => ({id: t.id, displayName: t.displayName}))),
);
const order = queryPreferencesByCategoryAndName(database, Preferences.CATEGORIES.TEAMS_ORDER).
const order = queryPreferencesByCategoryAndName(database, Preferences.TEAMS_ORDER).
observeWithColumns(['value']).pipe(
switchMap((p) => (p.length ? of$(p[0].value.split(',')) : of$([]))),
);

View File

@@ -7,8 +7,8 @@ import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import Preferences from '@constants/preferences';
import {getSidebarPreferenceAsBool} from '@helpers/api/preference';
import {querySidebarPreferences} from '@queries/servers/preference';
import {getPreferenceAsBool} from '@helpers/api/preference';
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
import {observeCurrentChannelId, observeCurrentTeamId, observeOnlyUnreads} from '@queries/servers/system';
import {observeUnreadsAndMentionsInTeam} from '@queries/servers/thread';
@@ -22,10 +22,10 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
return {
currentChannelId: observeCurrentChannelId(database),
groupUnreadsSeparately: querySidebarPreferences(database, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS).
groupUnreadsSeparately: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS).
observeWithColumns(['value']).
pipe(
switchMap((prefs: PreferenceModel[]) => of$(getSidebarPreferenceAsBool(prefs, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS))),
switchMap((prefs: PreferenceModel[]) => of$(getPreferenceAsBool(prefs, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.CHANNEL_SIDEBAR_GROUP_UNREADS, false))),
),
onlyUnreads: observeOnlyUnreads(database),
unreadsAndMentions: currentTeamId.pipe(

View File

@@ -928,506 +928,3 @@ exports[`components/channel_list_row should show results no tutorial 1`] = `
</View>
</RCTScrollView>
`;
exports[`components/channel_list_row should show results no tutorial 2 users 1`] = `
<RCTScrollView
ListEmptyComponent={[Function]}
ListFooterComponent={[Function]}
contentContainerStyle={
{
"flexGrow": 1,
}
}
data={
[
{
"data": [
{
"auth_service": "",
"create_at": 1111,
"delete_at": 0,
"email": "john@doe.com",
"first_name": "",
"id": "1",
"last_name": "",
"locale": "",
"nickname": "",
"notify_props": {
"channel": "true",
"comments": "never",
"desktop": "mention",
"desktop_sound": "true",
"email": "true",
"first_name": "true",
"mention_keys": "",
"push": "mention",
"push_status": "away",
},
"position": "",
"roles": "",
"update_at": 1111,
"username": "johndoe",
},
],
"first": true,
"id": "J",
},
{
"data": [
{
"auth_service": "",
"create_at": 1111,
"delete_at": 0,
"email": "rocky@doe.com",
"first_name": "",
"id": "2",
"last_name": "",
"locale": "",
"nickname": "",
"notify_props": {
"channel": "true",
"comments": "never",
"desktop": "mention",
"desktop_sound": "true",
"email": "true",
"first_name": "true",
"mention_keys": "",
"push": "mention",
"push_status": "away",
},
"position": "",
"roles": "",
"update_at": 1111,
"username": "rocky",
},
],
"first": false,
"id": "R",
},
]
}
getItem={[Function]}
getItemCount={[Function]}
initialNumToRender={15}
keyExtractor={[Function]}
keyboardDismissMode="on-drag"
keyboardShouldPersistTaps="always"
maxToRenderPerBatch={16}
onContentSizeChange={[Function]}
onEndReached={[Function]}
onLayout={[Function]}
onMomentumScrollBegin={[Function]}
onMomentumScrollEnd={[Function]}
onScroll={[Function]}
onScrollBeginDrag={[Function]}
onScrollEndDrag={[Function]}
removeClippedSubviews={true}
renderItem={[Function]}
scrollEventThrottle={60}
stickyHeaderIndices={[]}
style={
{
"backgroundColor": "#ffffff",
"flex": 1,
}
}
testID="UserListRow.section_list"
>
<View>
<View
onFocusCapture={[Function]}
onLayout={[Function]}
style={null}
>
<View
style={
{
"backgroundColor": "#ffffff",
}
}
>
<View
style={
{
"backgroundColor": "rgba(63,67,80,0.08)",
"height": 24,
"justifyContent": "center",
"paddingLeft": 16,
}
}
>
<Text
style={
{
"color": "#3f4350",
"fontFamily": "OpenSans-SemiBold",
"fontSize": 12,
"fontWeight": "600",
"lineHeight": 16,
}
}
>
J
</Text>
</View>
</View>
</View>
<View
onFocusCapture={[Function]}
onLayout={[Function]}
style={null}
>
<View
onMoveShouldSetResponder={[Function]}
onMoveShouldSetResponderCapture={[Function]}
onResponderEnd={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderReject={[Function]}
onResponderRelease={[Function]}
onResponderStart={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
onStartShouldSetResponderCapture={[Function]}
>
<View
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
>
<View
style={
{
"flex": 1,
"flexDirection": "row",
"height": 58,
"overflow": "hidden",
"paddingHorizontal": 20,
}
}
testID="create_direct_message.user_list.user_item.1"
>
<View
style={
[
{
"alignItems": "center",
"color": "#3f4350",
"flexDirection": "row",
},
{
"opacity": 1,
},
]
}
>
<View
style={
{
"borderRadius": 21.5,
"height": 42,
"width": 42,
}
}
testID="create_direct_message.user_list.user_item.1.profile_picture"
>
<Icon
name="account-outline"
size={24}
style={
{
"color": "rgba(63,67,80,0.48)",
}
}
/>
</View>
</View>
<View
style={
[
{
"flex": 1,
"flexDirection": "column",
"justifyContent": "center",
"paddingHorizontal": 10,
},
{
"opacity": 1,
},
]
}
>
<View
style={
{
"flexDirection": "row",
}
}
>
<Text
ellipsizeMode="tail"
numberOfLines={1}
style={
{
"color": "#3f4350",
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"height": 24,
"lineHeight": 24,
"maxWidth": "80%",
}
}
testID="create_direct_message.user_list.user_item.1.display_name"
>
johndoe
</Text>
</View>
</View>
<View
style={
{
"alignItems": "center",
"justifyContent": "center",
}
}
>
<Icon
color="rgba(63,67,80,0.32)"
name="circle-outline"
size={28}
/>
</View>
</View>
</View>
</View>
</View>
<View
onFocusCapture={[Function]}
onLayout={[Function]}
style={null}
/>
<View
onFocusCapture={[Function]}
onLayout={[Function]}
style={null}
>
<View
style={
{
"backgroundColor": "#ffffff",
}
}
>
<View
style={
{
"backgroundColor": "rgba(63,67,80,0.08)",
"height": 24,
"justifyContent": "center",
"paddingLeft": 16,
}
}
>
<Text
style={
{
"color": "#3f4350",
"fontFamily": "OpenSans-SemiBold",
"fontSize": 12,
"fontWeight": "600",
"lineHeight": 16,
}
}
>
R
</Text>
</View>
</View>
</View>
<View
onFocusCapture={[Function]}
onLayout={[Function]}
style={null}
>
<View
onMoveShouldSetResponder={[Function]}
onMoveShouldSetResponderCapture={[Function]}
onResponderEnd={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderReject={[Function]}
onResponderRelease={[Function]}
onResponderStart={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
onStartShouldSetResponderCapture={[Function]}
>
<View
accessibilityValue={
{
"max": undefined,
"min": undefined,
"now": undefined,
"text": undefined,
}
}
accessible={true}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
>
<View
style={
{
"flex": 1,
"flexDirection": "row",
"height": 58,
"overflow": "hidden",
"paddingHorizontal": 20,
}
}
testID="create_direct_message.user_list.user_item.2"
>
<View
style={
[
{
"alignItems": "center",
"color": "#3f4350",
"flexDirection": "row",
},
{
"opacity": 1,
},
]
}
>
<View
style={
{
"borderRadius": 21.5,
"height": 42,
"width": 42,
}
}
testID="create_direct_message.user_list.user_item.2.profile_picture"
>
<Icon
name="account-outline"
size={24}
style={
{
"color": "rgba(63,67,80,0.48)",
}
}
/>
</View>
</View>
<View
style={
[
{
"flex": 1,
"flexDirection": "column",
"justifyContent": "center",
"paddingHorizontal": 10,
},
{
"opacity": 1,
},
]
}
>
<View
style={
{
"flexDirection": "row",
}
}
>
<Text
ellipsizeMode="tail"
numberOfLines={1}
style={
{
"color": "#3f4350",
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"height": 24,
"lineHeight": 24,
"maxWidth": "80%",
}
}
testID="create_direct_message.user_list.user_item.2.display_name"
>
rocky
</Text>
</View>
</View>
<View
style={
{
"alignItems": "center",
"justifyContent": "center",
}
}
>
<Icon
color="rgba(63,67,80,0.32)"
name="circle-outline"
size={28}
/>
</View>
</View>
</View>
</View>
</View>
<View
onFocusCapture={[Function]}
onLayout={[Function]}
style={null}
/>
<View
onLayout={[Function]}
>
<View
style={
{
"alignItems": "center",
"flex": 1,
"justifyContent": "center",
}
}
>
<ActivityIndicator
color="#1c58d9"
size="large"
/>
</View>
</View>
</View>
</RCTScrollView>
`;

View File

@@ -38,34 +38,6 @@ describe('components/channel_list_row', () => {
push_status: 'away',
},
};
const user2: UserProfile = {
id: '2',
create_at: 1111,
update_at: 1111,
delete_at: 0,
username: 'rocky',
auth_service: '',
email: 'rocky@doe.com',
nickname: '',
first_name: '',
last_name: '',
position: '',
roles: '',
locale: '',
notify_props: {
channel: 'true',
comments: 'never',
desktop: 'mention',
desktop_sound: 'true',
email: 'true',
first_name: 'true',
mention_keys: '',
push: 'mention',
push_status: 'away',
},
};
beforeAll(async () => {
const server = await TestHelper.setupServerDatabase();
database = server.database;
@@ -119,30 +91,6 @@ describe('components/channel_list_row', () => {
expect(wrapper.toJSON()).toMatchSnapshot();
});
it('should show results no tutorial 2 users', () => {
const wrapper = renderWithEverything(
<UserList
profiles={[user, user2]}
testID='UserListRow'
currentUserId={'1'}
teammateNameDisplay={'johndoe'}
handleSelectProfile={() => {
// noop
}}
fetchMore={() => {
// noop
}}
loading={true}
selectedIds={{}}
showNoResults={true}
tutorialWatched={true}
/>,
{database},
);
expect(wrapper.toJSON()).toMatchSnapshot();
});
it('should show results and tutorial', () => {
const wrapper = renderWithEverything(
<UserList

View File

@@ -2,19 +2,17 @@
// See LICENSE.txt for license information.
import React, {useCallback, useMemo} from 'react';
import {defineMessages, IntlShape, useIntl} from 'react-intl';
import {useIntl} from 'react-intl';
import {FlatList, Keyboard, ListRenderItemInfo, Platform, SectionList, SectionListData, Text, View} from 'react-native';
import {storeProfile} from '@actions/local/user';
import Loading from '@components/loading';
import NoResultsWithButton from '@components/no_results_with_button';
import NoResultsWithTerm from '@components/no_results_with_term';
import UserListRow from '@components/user_list_row';
import {General, Screens} from '@constants';
import {useServerUrl} from '@context/server';
import {useTheme} from '@context/theme';
import {useKeyboardHeight} from '@hooks/device';
import {t} from '@i18n';
import {openAsBottomSheet} from '@screens/navigation';
import {
changeOpacity,
@@ -22,23 +20,9 @@ import {
} from '@utils/theme';
import {typography} from '@utils/typography';
type UserProfileWithChannelAdmin = UserProfile & {scheme_admin?: boolean}
type RenderItemType = ListRenderItemInfo<UserProfileWithChannelAdmin> & {section?: SectionListData<UserProfileWithChannelAdmin>}
const INITIAL_BATCH_TO_RENDER = 15;
const SCROLL_EVENT_THROTTLE = 60;
const messages = defineMessages({
admins: {
id: t('mobile.manage_members.section_title_admins'),
defaultMessage: 'CHANNEL ADMINS',
},
members: {
id: t('mobile.manage_members.section_title_members'),
defaultMessage: 'MEMBERS',
},
});
const keyboardDismissProp = Platform.select({
android: {
onScrollBeginDrag: Keyboard.dismiss,
@@ -57,58 +41,29 @@ const sectionKeyExtractor = (profile: UserProfile) => {
return profile.username[0].toUpperCase();
};
const sectionRoleKeyExtractor = (cAdmin: boolean) => {
// Group items by channel admin or channel member
return cAdmin ? messages.admins : messages.members;
};
export function createProfilesSections(profiles: UserProfile[]) {
const sections: {[key: string]: UserProfile[]} = {};
const sectionKeys: string[] = [];
for (const profile of profiles) {
const sectionKey = sectionKeyExtractor(profile);
export function createProfilesSections(intl: IntlShape, profiles: UserProfile[], members?: ChannelMember[]) {
if (!profiles.length) {
return [];
}
const sections = new Map();
if (members?.length) {
// when channel members are provided, build the sections by admins and members
const membersDictionary = new Map();
const membersSections = new Map();
const {formatMessage} = intl;
members.forEach((m) => membersDictionary.set(m.user_id, m));
profiles.forEach((p) => {
const member = membersDictionary.get(p.id);
const sectionKey = sectionRoleKeyExtractor(member.scheme_admin!);
const sectionValue = membersSections.get(sectionKey) || [];
// combine UserProfile and ChannelMember objects so can get channel member scheme_admin permission
const section = [...sectionValue, {...p, ...member}];
membersSections.set(sectionKey, section);
});
sections.set(formatMessage(messages.admins), membersSections.get(messages.admins));
sections.set(formatMessage(messages.members), membersSections.get(messages.members));
} else {
// when channel members are not provided, build the sections alphabetically
profiles.forEach((p) => {
const sectionKey = sectionKeyExtractor(p);
const sectionValue = sections.get(sectionKey) || [];
const section = [...sectionValue, p];
sections.set(sectionKey, section);
});
}
const results = [];
let index = 0;
for (const [k, v] of sections) {
if (v) {
results.push({
first: index === 0,
id: k,
data: v,
});
if (!sections[sectionKey]) {
sections[sectionKey] = [];
sectionKeys.push(sectionKey);
}
index++;
sections[sectionKey].push(profile);
}
return results;
sectionKeys.sort();
return sectionKeys.map((sectionKey, index) => {
return {
id: sectionKey,
first: index === 0,
data: sections[sectionKey],
};
});
}
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
@@ -148,14 +103,11 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
type Props = {
profiles: UserProfile[];
channelMembers?: ChannelMember[];
currentUserId: string;
teammateNameDisplay: string;
handleSelectProfile: (user: UserProfile) => void;
fetchMore: () => void;
loading: boolean;
manageMode?: boolean;
showManageMode?: boolean;
showNoResults: boolean;
selectedIds: {[id: string]: UserProfile};
testID?: string;
@@ -165,15 +117,12 @@ type Props = {
export default function UserList({
profiles,
channelMembers,
selectedIds,
currentUserId,
teammateNameDisplay,
handleSelectProfile,
fetchMore,
loading,
manageMode = false,
showManageMode = false,
showNoResults,
term,
testID,
@@ -184,22 +133,17 @@ export default function UserList({
const serverUrl = useServerUrl();
const style = getStyleFromTheme(theme);
const keyboardHeight = useKeyboardHeight();
const noResultsStyle = useMemo(() => [
const noResutsStyle = useMemo(() => [
style.noResultContainer,
{paddingBottom: keyboardHeight},
], [style, keyboardHeight]);
const data = useMemo(() => {
if (profiles.length === 0 && !loading) {
return [];
}
if (term) {
return profiles;
}
return createProfilesSections(intl, profiles, channelMembers);
}, [channelMembers, loading, profiles, term]);
return createProfilesSections(profiles);
}, [term, profiles]);
const openUserProfile = useCallback(async (profile: UserProfile) => {
const {user} = await storeProfile(serverUrl, profile);
@@ -218,34 +162,29 @@ export default function UserList({
}
}, []);
const renderItem = useCallback(({item, index, section}: RenderItemType) => {
const renderItem = useCallback(({item, index, section}: ListRenderItemInfo<UserProfile> & {section?: SectionListData<UserProfile>}) => {
// The list will re-render when the selection changes because it's passed into the list as extraData
const selected = Boolean(selectedIds[item.id]);
const canAdd = Object.keys(selectedIds).length < General.MAX_USERS_IN_GM;
const isChAdmin = item.scheme_admin || false;
return (
<UserListRow
key={item.id}
highlight={section?.first && index === 0}
id={item.id}
isChannelAdmin={isChAdmin}
isMyUser={currentUserId === item.id}
manageMode={manageMode}
onPress={handleSelectProfile}
onLongPress={openUserProfile}
selectable={manageMode || canAdd}
disabled={!canAdd}
selectable={true}
selected={selected}
showManageMode={showManageMode}
testID='create_direct_message.user_list.user_item'
teammateNameDisplay={teammateNameDisplay}
tutorialWatched={tutorialWatched}
user={item}
/>
);
}, [selectedIds, handleSelectProfile, showManageMode, manageMode, teammateNameDisplay, tutorialWatched]);
}, [selectedIds, currentUserId, handleSelectProfile, teammateNameDisplay, tutorialWatched]);
const renderLoading = useCallback(() => {
if (!loading) {
@@ -262,16 +201,16 @@ export default function UserList({
}, [loading, theme]);
const renderNoResults = useCallback(() => {
if (!showNoResults) {
if (!showNoResults || !term) {
return null;
}
return (
<View style={noResultsStyle}>
{term ? <NoResultsWithTerm term={term}/> : <NoResultsWithButton/>}
<View style={noResutsStyle}>
<NoResultsWithTerm term={term}/>
</View>
);
}, [showNoResults && style, term, noResultsStyle]);
}, [showNoResults && style, term, noResutsStyle]);
const renderSectionHeader = useCallback(({section}: {section: SectionListData<UserProfile>}) => {
return (

View File

@@ -12,7 +12,6 @@ import {
import {storeProfileLongPressTutorial} from '@actions/app/global';
import CompassIcon from '@components/compass_icon';
import FormattedText from '@components/formatted_text';
import ProfilePicture from '@components/profile_picture';
import {BotTag, GuestTag} from '@components/tag';
import TouchableWithFeedback from '@components/touchable_with_feedback';
@@ -20,27 +19,23 @@ import TutorialHighlight from '@components/tutorial_highlight';
import TutorialLongPress from '@components/tutorial_highlight/long_press';
import {useTheme} from '@context/theme';
import {useIsTablet} from '@hooks/device';
import {t} from '@i18n';
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
import {typography} from '@utils/typography';
import {displayUsername, isGuest} from '@utils/user';
type Props = {
highlight?: boolean;
id: string;
isMyUser: boolean;
isChannelAdmin: boolean;
manageMode: boolean;
onLongPress: (user: UserProfile) => void;
highlight?: boolean;
user: UserProfile;
teammateNameDisplay: string;
testID: string;
onPress?: (user: UserProfile) => void;
onLongPress: (user: UserProfile) => void;
selectable: boolean;
disabled?: boolean;
selected: boolean;
showManageMode: boolean;
teammateNameDisplay: string;
testID: string;
tutorialWatched?: boolean;
user: UserProfile;
}
const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
@@ -89,15 +84,6 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
alignItems: 'center',
justifyContent: 'center',
},
selectorManage: {
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'center',
},
manageText: {
color: changeOpacity(theme.centerChannelColor, 0.64),
...typography('Body', 100, 'Regular'),
},
tutorial: {
top: Platform.select({ios: -74, default: -94}),
},
@@ -114,26 +100,24 @@ function UserListRow({
id,
isMyUser,
highlight,
isChannelAdmin,
user,
teammateNameDisplay,
testID,
onPress,
onLongPress,
manageMode = false,
tutorialWatched = false,
selectable,
disabled,
selected,
showManageMode = false,
teammateNameDisplay,
testID,
tutorialWatched = false,
user,
}: Props) {
const theme = useTheme();
const intl = useIntl();
const isTablet = useIsTablet();
const [showTutorial, setShowTutorial] = useState(false);
const [itemBounds, setItemBounds] = useState<TutorialItemBounds>({startX: 0, startY: 0, endX: 0, endY: 0});
const viewRef = useRef<View>(null);
const style = getStyleFromTheme(theme);
const {formatMessage, locale} = useIntl();
const {formatMessage} = intl;
const {username} = user;
const startTutorial = () => {
@@ -168,41 +152,13 @@ function UserListRow({
}, [highlight, tutorialWatched, isTablet]);
const handlePress = useCallback(() => {
if (isMyUser && manageMode) {
return;
}
onPress?.(user);
}, [onPress, isMyUser, manageMode, user]);
}, [onPress, user]);
const handleLongPress = useCallback(() => {
onLongPress?.(user);
}, [onLongPress, user]);
const manageModeIcon = useMemo(() => {
if (!showManageMode || isMyUser) {
return null;
}
const color = changeOpacity(theme.centerChannelColor, 0.64);
const i18nId = isChannelAdmin ? t('mobile.manage_members.admin') : t('mobile.manage_members.member');
const defaultMessage = isChannelAdmin ? 'Admin' : 'Member';
return (
<View style={style.selectorManage}>
<FormattedText
id={i18nId}
style={style.manageText}
defaultMessage={defaultMessage}
/>
<CompassIcon
name={'chevron-down'}
size={18}
color={color}
/>
</View>
);
}, [isChannelAdmin, showManageMode, theme]);
const onLayout = useCallback(() => {
startTutorial();
}, []);
@@ -233,7 +189,7 @@ function UserListRow({
}, {username});
}
const teammateDisplay = displayUsername(user, locale, teammateNameDisplay);
const teammateDisplay = displayUsername(user, intl.locale, teammateNameDisplay);
const showTeammateDisplay = teammateDisplay !== username;
const userItemTestID = `${testID}.${id}`;
@@ -301,7 +257,7 @@ function UserListRow({
</View>
}
</View>
{manageMode ? manageModeIcon : icon}
{icon}
</View>
</TouchableWithFeedback>
{showTutorial &&
@@ -311,7 +267,7 @@ function UserListRow({
onLayout={onLayout}
>
<TutorialLongPress
message={formatMessage({id: 'user.tutorial.long_press', defaultMessage: "Long-press on an item to view a user's profile"})}
message={intl.formatMessage({id: 'user.tutorial.long_press', defaultMessage: "Long-press on an item to view a user's profile"})}
style={isTablet ? style.tutorialTablet : style.tutorial}
/>
</TutorialHighlight>

View File

@@ -15,8 +15,6 @@ export default keyMirror({
LEAVE_TEAM: null,
LOADING_CHANNEL_POSTS: null,
NOTIFICATION_ERROR: null,
REMOVE_USER_FROM_CHANNEL: null,
MANAGE_USER_CHANGE_ROLE: null,
SERVER_LOGOUT: null,
SERVER_VERSION_CHANGED: null,
SESSION_EXPIRED: null,

View File

@@ -29,7 +29,6 @@ export default {
SHOW_FULLNAME: 'full_name',
},
SPECIAL_MENTIONS: new Set(['all', 'channel', 'here']),
MAX_USERS_ADD_TO_CHANNEL: 25,
MAX_USERS_IN_GM: 7,
MIN_USERS_IN_GM: 3,
MAX_GROUP_CHANNELS_FOR_PROFILES: 50,

View File

@@ -19,7 +19,6 @@ import Integrations from './integrations';
import Launch from './launch';
import License from './license';
import List from './list';
import Members from './members';
import Navigation from './navigation';
import Network from './network';
import NotificationLevel from './notification_level';
@@ -58,7 +57,6 @@ export {
Launch,
License,
List,
Members,
Navigation,
Network,
NotificationLevel,

View File

@@ -1,16 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import keyMirror from '@utils/key_mirror';
const ManageOptions = keyMirror({
REMOVE_USER: null,
MAKE_CHANNEL_ADMIN: null,
MAKE_CHANNEL_MEMBER: null,
});
export type ManageOptionsTypes = keyof typeof ManageOptions
export default {
ManageOptions,
};

View File

@@ -1,26 +1,16 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
export const CATEGORIES_TO_KEEP: Record<string, string> = {
ADVANCED_SETTINGS: 'advanced_settings',
DIRECT_CHANNEL_SHOW: 'direct_channel_show',
GROUP_CHANNEL_SHOW: 'group_channel_show',
DISPLAY_SETTINGS: 'display_settings',
EMOJI: 'emoji',
NOTIFICATIONS: 'notifications',
SAVED_POST: 'flagged_post',
SIDEBAR_SETTINGS: 'sidebar_settings',
TEAMS_ORDER: 'teams_order',
THEME: 'theme',
};
const CATEGORIES: Record<string, string> = {
...CATEGORIES_TO_KEEP,
FAVORITE_CHANNEL: 'favorite_channel',
};
const Preferences = {
CATEGORIES,
const Preferences: Record<string, any> = {
CATEGORY_CHANNEL_OPEN_TIME: 'channel_open_time',
CATEGORY_CHANNEL_APPROXIMATE_VIEW_TIME: 'channel_approximate_view_time',
CATEGORY_DIRECT_CHANNEL_SHOW: 'direct_channel_show',
CATEGORY_GROUP_CHANNEL_SHOW: 'group_channel_show',
CATEGORY_EMOJI: 'emoji',
CATEGORY_SAVED_POST: 'flagged_post',
CATEGORY_FAVORITE_CHANNEL: 'favorite_channel',
CATEGORY_AUTO_RESET_MANUAL_STATUS: 'auto_reset_manual_status',
CATEGORY_NOTIFICATIONS: 'notifications',
COLLAPSED_REPLY_THREADS: 'collapsed_reply_threads',
COLLAPSED_REPLY_THREADS_OFF: 'off',
COLLAPSED_REPLY_THREADS_ON: 'on',
@@ -37,6 +27,7 @@ const Preferences = {
// "immediate" is a 30 second interval
INTERVAL_NEVER: 0,
INTERVAL_NOT_SET: -1,
CATEGORY_DISPLAY_SETTINGS: 'display_settings',
NAME_NAME_FORMAT: 'name_format',
DISPLAY_PREFER_NICKNAME: 'nickname_full_name',
DISPLAY_PREFER_FULL_NAME: 'full_name',
@@ -45,14 +36,18 @@ const Preferences = {
LINK_PREVIEW_DISPLAY: 'link_previews',
MENTION_KEYS: 'mention_keys',
USE_MILITARY_TIME: 'use_military_time',
CATEGORY_SIDEBAR_SETTINGS: 'sidebar_settings',
CHANNEL_SIDEBAR_ORGANIZATION: 'channel_sidebar_organization',
CHANNEL_SIDEBAR_LIMIT_DMS: 'limit_visible_dms_gms',
CHANNEL_SIDEBAR_LIMIT_DMS_DEFAULT: 20,
CHANNEL_SIDEBAR_GROUP_UNREADS: 'show_unread_section',
AUTOCLOSE_DMS_ENABLED: 'after_seven_days',
CATEGORY_ADVANCED_SETTINGS: 'advanced_settings',
ADVANCED_FILTER_JOIN_LEAVE: 'join_leave',
ADVANCED_CODE_BLOCK_ON_CTRL_ENTER: 'code_block_ctrl_enter',
ADVANCED_SEND_ON_CTRL_ENTER: 'send_on_ctrl_enter',
CATEGORY_THEME: 'theme',
TEAMS_ORDER: 'teams_order',
THEMES: {
denim: {
type: 'Denim',

View File

@@ -11,6 +11,7 @@ export const CHANNEL = 'Channel';
export const CHANNEL_ADD_PEOPLE = 'ChannelAddPeople';
export const CHANNEL_INFO = 'ChannelInfo';
export const CHANNEL_MENTION = 'ChannelMention';
export const CHANNEL_NOTIFICATION_PREFERENCE = 'ChannelNotificationPreference';
export const CODE = 'Code';
export const CREATE_DIRECT_MESSAGE = 'CreateDirectMessage';
export const CREATE_OR_EDIT_CHANNEL = 'CreateOrEditChannel';
@@ -33,7 +34,6 @@ export const IN_APP_NOTIFICATION = 'InAppNotification';
export const JOIN_TEAM = 'JoinTeam';
export const LATEX = 'Latex';
export const LOGIN = 'Login';
export const MANAGE_CHANNEL_MEMBERS = 'ManageChannelMembers';
export const MENTIONS = 'Mentions';
export const MFA = 'MFA';
export const ONBOARDING = 'Onboarding';
@@ -80,6 +80,7 @@ export default {
CHANNEL_ADD_PEOPLE,
CHANNEL_INFO,
CHANNEL_MENTION,
CHANNEL_NOTIFICATION_PREFERENCE,
CODE,
CREATE_DIRECT_MESSAGE,
CREATE_OR_EDIT_CHANNEL,
@@ -102,7 +103,6 @@ export default {
JOIN_TEAM,
LATEX,
LOGIN,
MANAGE_CHANNEL_MEMBERS,
MENTIONS,
MFA,
ONBOARDING,
@@ -142,7 +142,6 @@ export default {
export const MODAL_SCREENS_WITHOUT_BACK = new Set<string>([
BROWSE_CHANNELS,
CHANNEL_INFO,
CHANNEL_ADD_PEOPLE,
CREATE_DIRECT_MESSAGE,
CREATE_TEAM,
CUSTOM_STATUS,
@@ -151,7 +150,6 @@ export const MODAL_SCREENS_WITHOUT_BACK = new Set<string>([
EDIT_SERVER,
FIND_CHANNELS,
GALLERY,
MANAGE_CHANNEL_MEMBERS,
INVITE,
PERMALINK,
]);
@@ -172,6 +170,7 @@ export const SCREENS_AS_BOTTOM_SHEET = new Set<string>([
]);
export const NOT_READY = [
CHANNEL_ADD_PEOPLE,
CHANNEL_MENTION,
CREATE_TEAM,
];

View File

@@ -5,12 +5,10 @@ import {t} from '@i18n';
import keyMirror from '@utils/key_mirror';
export const SNACK_BAR_TYPE = keyMirror({
ADD_CHANNEL_MEMBERS: null,
FAVORITE_CHANNEL: null,
LINK_COPIED: null,
MESSAGE_COPIED: null,
MUTE_CHANNEL: null,
REMOVE_CHANNEL_USER: null,
UNFAVORITE_CHANNEL: null,
UNMUTE_CHANNEL: null,
});
@@ -23,12 +21,6 @@ type SnackBarConfig = {
};
export const SNACK_BAR_CONFIG: Record<string, SnackBarConfig> = {
ADD_CHANNEL_MEMBERS: {
id: t('snack.bar.channel.members.added'),
defaultMessage: '{numMembers, number} {numMembers, plural, one {member} other {members}} added',
iconName: 'check',
canUndo: false,
},
FAVORITE_CHANNEL: {
id: t('snack.bar.favorited.channel'),
defaultMessage: 'This channel was favorited',
@@ -53,12 +45,6 @@ export const SNACK_BAR_CONFIG: Record<string, SnackBarConfig> = {
iconName: 'bell-off-outline',
canUndo: true,
},
REMOVE_CHANNEL_USER: {
id: t('snack.bar.remove.user'),
defaultMessage: '1 member was removed from the channel',
iconName: 'check',
canUndo: true,
},
UNFAVORITE_CHANNEL: {
id: t('snack.bar.unfavorite.channel'),
defaultMessage: 'This channel was unfavorited',

View File

@@ -6,7 +6,7 @@ import React, {ComponentType, createContext, useEffect, useState} from 'react';
import {Appearance} from 'react-native';
import {Preferences} from '@constants';
import {queryThemePreferences} from '@queries/servers/preference';
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
import {observeCurrentTeamId} from '@queries/servers/system';
import {setThemeDefaults, updateThemeIfNeeded} from '@utils/theme';
@@ -97,7 +97,7 @@ export function useTheme(): Theme {
const enhancedThemeProvider = withObservables([], ({database}: {database: Database}) => ({
currentTeamId: observeCurrentTeamId(database),
themes: queryThemePreferences(database).observeWithColumns(['value']),
themes: queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_THEME).observeWithColumns(['value']),
}));
export default enhancedThemeProvider(ThemeProvider);

View File

@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Database, Q} from '@nozbe/watermelondb';
import {Database, Model, Q} from '@nozbe/watermelondb';
import LokiJSAdapter from '@nozbe/watermelondb/adapters/lokijs';
import logger from '@nozbe/watermelondb/utils/common/logger';
import {DeviceEventEmitter, Platform} from 'react-native';
@@ -45,14 +45,14 @@ class DatabaseManager {
private readonly serverModels: Models;
constructor() {
this.appModels = [InfoModel, GlobalModel, ServersModel];
this.appModels = [InfoModel, GlobalModel, ServersModel] as unknown[] as Model[];
this.serverModels = [
CategoryModel, CategoryChannelModel, ChannelModel, ChannelInfoModel, ChannelMembershipModel, ConfigModel, CustomEmojiModel, DraftModel, FileModel,
GroupModel, GroupChannelModel, GroupTeamModel, GroupMembershipModel, MyChannelModel, MyChannelSettingsModel, MyTeamModel,
PostModel, PostsInChannelModel, PostsInThreadModel, PreferenceModel, ReactionModel, RoleModel,
SystemModel, TeamModel, TeamChannelHistoryModel, TeamMembershipModel, TeamSearchHistoryModel,
ThreadModel, ThreadParticipantModel, ThreadInTeamModel, TeamThreadsSyncModel, UserModel,
];
] as unknown[] as Model[];
this.databaseDirectory = '';
}

View File

@@ -43,14 +43,14 @@ class DatabaseManager {
private readonly serverModels: Models;
constructor() {
this.appModels = [InfoModel, GlobalModel, ServersModel];
this.appModels = [InfoModel, GlobalModel, ServersModel] as unknown[] as Models;
this.serverModels = [
CategoryModel, CategoryChannelModel, ChannelModel, ChannelInfoModel, ChannelMembershipModel, ConfigModel, CustomEmojiModel, DraftModel, FileModel,
GroupModel, GroupChannelModel, GroupTeamModel, GroupMembershipModel, MyChannelModel, MyChannelSettingsModel, MyTeamModel,
PostModel, PostsInChannelModel, PostsInThreadModel, PreferenceModel, ReactionModel, RoleModel,
SystemModel, TeamModel, TeamChannelHistoryModel, TeamMembershipModel, TeamSearchHistoryModel,
ThreadModel, ThreadParticipantModel, ThreadInTeamModel, TeamThreadsSyncModel, UserModel,
];
] as unknown[] as Models;
this.databaseDirectory = Platform.OS === 'ios' ? getIOSAppGroupDetails().appGroupDatabase : `${FileSystem.DocumentDirectoryPath}/databases/`;
}

View File

@@ -56,7 +56,7 @@ describe('** APP DATA OPERATOR **', () => {
],
tableName: 'Info',
prepareRecordsOnly: false,
}, 'handleInfo');
});
});
it('=> HandleGlobal: should write to GLOBAL table', async () => {
@@ -79,6 +79,6 @@ describe('** APP DATA OPERATOR **', () => {
createOrUpdateRawValues: globals,
tableName: 'Global',
prepareRecordsOnly: false,
}, 'handleGlobal');
});
});
});

View File

@@ -28,7 +28,7 @@ export default class AppDataOperator extends BaseDataOperator {
prepareRecordsOnly,
createOrUpdateRawValues: getUniqueRawsBy({raws: info, key: 'version_number'}),
tableName: INFO,
}, 'handleInfo');
});
};
handleGlobal = async ({globals, prepareRecordsOnly = true}: HandleGlobalArgs) => {
@@ -45,6 +45,6 @@ export default class AppDataOperator extends BaseDataOperator {
prepareRecordsOnly,
createOrUpdateRawValues: getUniqueRawsBy({raws: globals, key: 'id'}),
tableName: GLOBAL,
}, 'handleGlobal');
});
};
}

View File

@@ -22,10 +22,10 @@ import type {
export interface BaseDataOperatorType {
database: Database;
handleRecords: <T extends Model>({buildKeyRecordBy, fieldName, transformer, createOrUpdateRawValues, deleteRawValues, tableName, prepareRecordsOnly}: HandleRecordsArgs<T>, description: string) => Promise<Model[]>;
processRecords: <T extends Model>({createOrUpdateRawValues, deleteRawValues, tableName, buildKeyRecordBy, fieldName}: ProcessRecordsArgs) => Promise<ProcessRecordResults<T>>;
batchRecords: (models: Model[], description: string) => Promise<void>;
prepareRecords: <T extends Model>({tableName, createRaws, deleteRaws, updateRaws, transformer}: OperationArgs<T>) => Promise<Model[]>;
handleRecords: ({buildKeyRecordBy, fieldName, transformer, createOrUpdateRawValues, deleteRawValues, tableName, prepareRecordsOnly}: HandleRecordsArgs) => Promise<Model[]>;
processRecords: ({createOrUpdateRawValues, deleteRawValues, tableName, buildKeyRecordBy, fieldName}: ProcessRecordsArgs) => Promise<ProcessRecordResults>;
batchRecords: (models: Model[]) => Promise<void>;
prepareRecords: ({tableName, createRaws, deleteRaws, updateRaws, transformer}: OperationArgs) => Promise<Model[]>;
}
export default class BaseDataOperator {
@@ -45,7 +45,7 @@ export default class BaseDataOperator {
* @param {(existing: Model, newElement: RawValue) => boolean} inputsArg.buildKeyRecordBy
* @returns {Promise<{ProcessRecordResults}>}
*/
processRecords = async <T extends Model>({createOrUpdateRawValues = [], deleteRawValues = [], tableName, buildKeyRecordBy, fieldName}: ProcessRecordsArgs): Promise<ProcessRecordResults<T>> => {
processRecords = async ({createOrUpdateRawValues = [], deleteRawValues = [], tableName, buildKeyRecordBy, fieldName}: ProcessRecordsArgs): Promise<ProcessRecordResults> => {
const getRecords = async (rawValues: RawValue[]) => {
// We will query a table where one of its fields can match a range of values. Hence, here we are extracting all those potential values.
const columnValues: string[] = getRangeOfValues({fieldName, raws: rawValues});
@@ -60,7 +60,7 @@ export default class BaseDataOperator {
return [];
}
const existingRecords = await retrieveRecords<T>({
const existingRecords = await retrieveRecords({
database: this.database,
tableName,
condition: Q.where(fieldName, Q.oneOf(columnValues)),
@@ -125,13 +125,13 @@ export default class BaseDataOperator {
* @param {(TransformerArgs) => Promise<Model>;} transformer
* @returns {Promise<Model[]>}
*/
prepareRecords = async <T extends Model>({tableName, createRaws, deleteRaws, updateRaws, transformer}: OperationArgs<T>): Promise<T[]> => {
prepareRecords = async ({tableName, createRaws, deleteRaws, updateRaws, transformer}: OperationArgs): Promise<Model[]> => {
if (!this.database) {
logWarning('Database not defined in prepareRecords');
return [];
}
let preparedRecords: Array<Promise<T>> = [];
let preparedRecords: Array<Promise<Model>> = [];
// create operation
if (createRaws?.length) {
@@ -165,7 +165,7 @@ export default class BaseDataOperator {
preparedRecords = preparedRecords.concat(recordPromises);
}
const results = (await Promise.all(preparedRecords)) as T[];
const results = await Promise.all(preparedRecords);
if (deleteRaws?.length) {
deleteRaws.forEach((deleteRecord) => {
@@ -182,12 +182,12 @@ export default class BaseDataOperator {
* @param {Array} models
* @returns {Promise<void>}
*/
async batchRecords(models: Model[], description: string): Promise<void> {
async batchRecords(models: Model[]): Promise<void> {
try {
if (models.length > 0) {
await this.database.write(async (writer) => {
await writer.batch(models);
}, description);
await writer.batch(...models);
});
}
} catch (e) {
logWarning('batchRecords error ', e as Error);
@@ -205,7 +205,7 @@ export default class BaseDataOperator {
* @param {string} handleRecordsArgs.tableName
* @returns {Promise<Model[]>}
*/
async handleRecords<T extends Model>({buildKeyRecordBy, fieldName, transformer, createOrUpdateRawValues, deleteRawValues = [], tableName, prepareRecordsOnly = true}: HandleRecordsArgs<T>, description: string): Promise<T[]> {
async handleRecords({buildKeyRecordBy, fieldName, transformer, createOrUpdateRawValues, deleteRawValues = [], tableName, prepareRecordsOnly = true}: HandleRecordsArgs): Promise<Model[]> {
if (!createOrUpdateRawValues.length) {
logWarning(
`An empty "rawValues" array has been passed to the handleRecords method for tableName ${tableName}`,
@@ -213,7 +213,7 @@ export default class BaseDataOperator {
return [];
}
const {createRaws, deleteRaws, updateRaws} = await this.processRecords<T>({
const {createRaws, deleteRaws, updateRaws} = await this.processRecords({
createOrUpdateRawValues,
deleteRawValues,
tableName,
@@ -221,8 +221,8 @@ export default class BaseDataOperator {
fieldName,
});
let models: T[] = [];
models = await this.prepareRecords<T>({
let models: Model[] = [];
models = await this.prepareRecords({
tableName,
createRaws,
updateRaws,
@@ -231,7 +231,7 @@ export default class BaseDataOperator {
});
if (!prepareRecordsOnly && models?.length) {
await this.batchRecords(models, description);
await this.batchRecords(models);
}
return models;

View File

@@ -46,7 +46,7 @@ describe('*** Operator: Category Handlers tests ***', () => {
tableName: MM_TABLES.SERVER.CATEGORY,
prepareRecordsOnly: false,
transformer: transformCategoryRecord,
}, 'handleCategories');
});
});
it('=> handleCategoryChannels: should write to the CATEGORY_CHANNEL table', async () => {
@@ -74,6 +74,6 @@ describe('*** Operator: Category Handlers tests ***', () => {
tableName: MM_TABLES.SERVER.CATEGORY_CHANNEL,
prepareRecordsOnly: false,
transformer: transformCategoryChannelRecord,
}, 'handleCategoryChannels');
});
});
});

View File

@@ -11,7 +11,6 @@ import {
import {getUniqueRawsBy} from '@database/operator/utils/general';
import {logWarning} from '@utils/log';
import type ServerDataOperatorBase from '.';
import type {
HandleCategoryChannelArgs,
HandleCategoryArgs,
@@ -29,7 +28,7 @@ export interface CategoryHandlerMix {
handleCategories: ({categories, prepareRecordsOnly}: HandleCategoryArgs) => Promise<CategoryModel[]>;
}
const CategoryHandler = <TBase extends Constructor<ServerDataOperatorBase>>(superclass: TBase) => class extends superclass {
const CategoryHandler = (superclass: any) => class extends superclass {
/**
* handleCategories: Handler responsible for the Create/Update operations occurring on the Category table from the 'Server' schema
* @param {HandleCategoryArgs} categoriesArgs
@@ -79,7 +78,7 @@ const CategoryHandler = <TBase extends Constructor<ServerDataOperatorBase>>(supe
createOrUpdateRawValues,
tableName: CATEGORY,
prepareRecordsOnly,
}, 'handleCategories');
});
};
/**
@@ -89,7 +88,7 @@ const CategoryHandler = <TBase extends Constructor<ServerDataOperatorBase>>(supe
* @param {boolean} categoriesArgs.prepareRecordsOnly
* @returns {Promise<CategoryChannelModel[]>}
*/
handleCategoryChannels = async ({categoryChannels, prepareRecordsOnly = true}: HandleCategoryChannelArgs): Promise<CategoryChannelModel[]> => {
handleCategoryChannels = async ({categoryChannels, prepareRecordsOnly = true}: HandleCategoryChannelArgs): Promise<CategoryModel[]> => {
if (!categoryChannels?.length) {
logWarning(
'An empty or undefined "categoryChannels" array has been passed to the handleCategories method',
@@ -106,7 +105,7 @@ const CategoryHandler = <TBase extends Constructor<ServerDataOperatorBase>>(supe
createOrUpdateRawValues,
tableName: CATEGORY_CHANNEL,
prepareRecordsOnly,
}, 'handleCategoryChannels');
});
};
};

View File

@@ -63,7 +63,7 @@ describe('*** Operator: Channel Handlers tests ***', () => {
tableName: 'Channel',
prepareRecordsOnly: false,
transformer: transformChannelRecord,
}, 'handleChannel');
});
});
it('=> HandleMyChannelSettings: should write to the MY_CHANNEL_SETTINGS table', async () => {
@@ -103,7 +103,7 @@ describe('*** Operator: Channel Handlers tests ***', () => {
prepareRecordsOnly: false,
buildKeyRecordBy: buildMyChannelKey,
transformer: transformMyChannelSettingsRecord,
}, 'handleMyChannelSettings');
});
});
it('=> HandleChannelInfo: should write to the CHANNEL_INFO table', async () => {
@@ -134,7 +134,7 @@ describe('*** Operator: Channel Handlers tests ***', () => {
tableName: 'ChannelInfo',
prepareRecordsOnly: false,
transformer: transformChannelInfoRecord,
}, 'handleChannelInfo');
});
});
it('=> HandleMyChannel: should write to the MY_CHANNEL table', async () => {
@@ -195,7 +195,7 @@ describe('*** Operator: Channel Handlers tests ***', () => {
prepareRecordsOnly: false,
buildKeyRecordBy: buildMyChannelKey,
transformer: transformMyChannelRecord,
}, 'handleMyChannel');
});
});
it('=> HandleMyChannel: should keep the previous lastFetchedAt for MY_CHANNEL', async () => {
@@ -314,6 +314,6 @@ describe('*** Operator: Channel Handlers tests ***', () => {
prepareRecordsOnly: false,
buildKeyRecordBy: buildChannelMembershipKey,
transformer: transformChannelMembershipRecord,
}, 'handleChannelMembership');
});
});
});

View File

@@ -19,7 +19,6 @@ import {getUniqueRawsBy} from '@database/operator/utils/general';
import {getIsCRTEnabled} from '@queries/servers/thread';
import {logWarning} from '@utils/log';
import type ServerDataOperatorBase from '.';
import type {HandleChannelArgs, HandleChannelInfoArgs, HandleChannelMembershipArgs, HandleMyChannelArgs, HandleMyChannelSettingsArgs} from '@typings/database/database';
import type ChannelModel from '@typings/database/models/servers/channel';
import type ChannelInfoModel from '@typings/database/models/servers/channel_info';
@@ -43,7 +42,7 @@ export interface ChannelHandlerMix {
handleMyChannel: ({channels, myChannels, isCRTEnabled, prepareRecordsOnly}: HandleMyChannelArgs) => Promise<MyChannelModel[]>;
}
const ChannelHandler = <TBase extends Constructor<ServerDataOperatorBase>>(superclass: TBase) => class extends superclass {
const ChannelHandler = (superclass: any) => class extends superclass {
/**
* handleChannel: Handler responsible for the Create/Update operations occurring on the CHANNEL table from the 'Server' schema
* @param {HandleChannelArgs} channelsArgs
@@ -90,7 +89,7 @@ const ChannelHandler = <TBase extends Constructor<ServerDataOperatorBase>>(super
prepareRecordsOnly,
createOrUpdateRawValues,
tableName: CHANNEL,
}, 'handleChannel');
});
};
/**
@@ -147,7 +146,7 @@ const ChannelHandler = <TBase extends Constructor<ServerDataOperatorBase>>(super
prepareRecordsOnly,
createOrUpdateRawValues,
tableName: MY_CHANNEL_SETTINGS,
}, 'handleMyChannelSettings');
});
};
/**
@@ -206,7 +205,7 @@ const ChannelHandler = <TBase extends Constructor<ServerDataOperatorBase>>(super
prepareRecordsOnly,
createOrUpdateRawValues,
tableName: CHANNEL_INFO,
}, 'handleChannelInfo');
});
};
/**
@@ -293,7 +292,7 @@ const ChannelHandler = <TBase extends Constructor<ServerDataOperatorBase>>(super
prepareRecordsOnly,
createOrUpdateRawValues,
tableName: MY_CHANNEL,
}, 'handleMyChannel');
});
};
/**
@@ -349,7 +348,7 @@ const ChannelHandler = <TBase extends Constructor<ServerDataOperatorBase>>(super
prepareRecordsOnly,
createOrUpdateRawValues,
tableName: CHANNEL_MEMBERSHIP,
}, 'handleChannelMembership');
});
};
};

View File

@@ -49,6 +49,6 @@ describe('*** Operator: Group Handlers tests ***', () => {
tableName: MM_TABLES.SERVER.GROUP,
prepareRecordsOnly: false,
transformer: transformGroupRecord,
}, 'handleGroups');
});
});
});

View File

@@ -8,7 +8,6 @@ import {queryGroupChannelForChannel, queryGroupMembershipForMember, queryGroupTe
import {generateGroupAssociationId} from '@utils/groups';
import {logWarning} from '@utils/log';
import type ServerDataOperatorBase from '.';
import type {HandleGroupArgs, HandleGroupChannelsForChannelArgs, HandleGroupMembershipForMemberArgs, HandleGroupTeamsForTeamArgs} from '@typings/database/database';
import type GroupModel from '@typings/database/models/servers/group';
import type GroupChannelModel from '@typings/database/models/servers/group_channel';
@@ -24,7 +23,7 @@ export interface GroupHandlerMix {
handleGroupTeamsForTeam: ({teamId, groups, prepareRecordsOnly}: HandleGroupTeamsForTeamArgs) => Promise<GroupTeamModel[]>;
}
const GroupHandler = <TBase extends Constructor<ServerDataOperatorBase>>(superclass: TBase) => class extends superclass implements GroupHandlerMix {
const GroupHandler = (superclass: any) => class extends superclass implements GroupHandlerMix {
/**
* handleGroups: Handler responsible for the Create/Update operations occurring on the Group table from the 'Server' schema
*
@@ -47,7 +46,7 @@ const GroupHandler = <TBase extends Constructor<ServerDataOperatorBase>>(supercl
createOrUpdateRawValues,
tableName: GROUP,
prepareRecordsOnly,
}, 'handleGroups');
});
};
/**
@@ -94,14 +93,14 @@ const GroupHandler = <TBase extends Constructor<ServerDataOperatorBase>>(supercl
records.push(...(await this.handleRecords({
fieldName: 'id',
transformer: transformGroupChannelRecord,
createOrUpdateRawValues: rawValues,
rawValues,
tableName: GROUP_CHANNEL,
prepareRecordsOnly: true,
}, 'handleGroupChannelsForChannel')));
})));
// Batch update if there are records
if (records.length && !prepareRecordsOnly) {
await this.batchRecords(records, 'handleGroupChannelsForChannel');
await this.batchRecords(records);
}
return records;
@@ -151,14 +150,14 @@ const GroupHandler = <TBase extends Constructor<ServerDataOperatorBase>>(supercl
records.push(...(await this.handleRecords({
fieldName: 'id',
transformer: transformGroupMembershipRecord,
createOrUpdateRawValues: rawValues,
rawValues,
tableName: GROUP_MEMBERSHIP,
prepareRecordsOnly: true,
}, 'handleGroupMembershipsForMember')));
})));
// Batch update if there are records
if (records.length && !prepareRecordsOnly) {
await this.batchRecords(records, 'handleGroupMembershipsForMember');
await this.batchRecords(records);
}
return records;
@@ -208,14 +207,14 @@ const GroupHandler = <TBase extends Constructor<ServerDataOperatorBase>>(supercl
records.push(...(await this.handleRecords({
fieldName: 'id',
transformer: transformGroupTeamRecord,
createOrUpdateRawValues: rawValues,
rawValues,
tableName: GROUP_TEAM,
prepareRecordsOnly: true,
}, 'handleGroupTeamsForTeam')));
})));
// Batch update if there are records
if (records.length && !prepareRecordsOnly) {
await this.batchRecords(records, 'handleGroupTeamsForTeam');
await this.batchRecords(records);
}
return records;

Some files were not shown because too many files have changed in this diff Show More